about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/administration/cleaning-store.chapter.md4
-rw-r--r--nixos/doc/manual/administration/containers.chapter.md28
-rw-r--r--nixos/doc/manual/administration/containers.xml34
-rw-r--r--nixos/doc/manual/administration/running.xml4
-rw-r--r--nixos/doc/manual/administration/troubleshooting.chapter.md12
-rw-r--r--nixos/doc/manual/administration/troubleshooting.xml16
-rw-r--r--nixos/doc/manual/configuration/config-syntax.chapter.md19
-rw-r--r--nixos/doc/manual/configuration/config-syntax.xml25
-rw-r--r--nixos/doc/manual/configuration/configuration.xml10
-rw-r--r--nixos/doc/manual/configuration/declarative-packages.section.md46
-rw-r--r--nixos/doc/manual/configuration/declarative-packages.xml54
-rw-r--r--nixos/doc/manual/configuration/file-systems.chapter.md42
-rw-r--r--nixos/doc/manual/configuration/file-systems.xml58
-rw-r--r--nixos/doc/manual/configuration/linux-kernel.chapter.md37
-rw-r--r--nixos/doc/manual/configuration/networking.chapter.md16
-rw-r--r--nixos/doc/manual/configuration/networking.xml20
-rw-r--r--nixos/doc/manual/configuration/package-mgmt.chapter.md18
-rw-r--r--nixos/doc/manual/configuration/package-mgmt.xml31
-rw-r--r--nixos/doc/manual/configuration/profiles.chapter.md34
-rw-r--r--nixos/doc/manual/configuration/profiles.xml39
-rw-r--r--nixos/doc/manual/development/development.xml4
-rw-r--r--nixos/doc/manual/development/nixos-tests.chapter.md13
-rw-r--r--nixos/doc/manual/development/nixos-tests.xml20
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md10
-rw-r--r--nixos/doc/manual/development/writing-modules.chapter.md166
-rw-r--r--nixos/doc/manual/development/writing-modules.xml191
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md14
-rw-r--r--nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml3
-rw-r--r--nixos/doc/manual/from_md/administration/containers.chapter.xml31
-rw-r--r--nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml12
-rw-r--r--nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml21
-rw-r--r--nixos/doc/manual/from_md/configuration/declarative-packages.section.xml53
-rw-r--r--nixos/doc/manual/from_md/configuration/file-systems.chapter.xml55
-rw-r--r--nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml37
-rw-r--r--nixos/doc/manual/from_md/configuration/networking.chapter.xml15
-rw-r--r--nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml28
-rw-r--r--nixos/doc/manual/from_md/configuration/profiles.chapter.xml38
-rw-r--r--nixos/doc/manual/from_md/development/nixos-tests.chapter.xml14
-rw-r--r--nixos/doc/manual/from_md/development/option-declarations.section.xml20
-rw-r--r--nixos/doc/manual/from_md/development/writing-modules.chapter.xml196
-rw-r--r--nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml24
-rw-r--r--nixos/doc/manual/from_md/installation/installing.chapter.xml645
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2009.section.xml10
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2111.section.xml888
-rw-r--r--nixos/doc/manual/installation/installation.xml2
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md482
-rw-r--r--nixos/doc/manual/installation/installing.xml616
-rw-r--r--nixos/doc/manual/man-nixos-rebuild.xml16
-rwxr-xr-xnixos/doc/manual/md-to-db.sh6
-rw-r--r--nixos/doc/manual/release-notes/rl-2009.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md351
-rw-r--r--nixos/lib/build-vms.nix25
-rw-r--r--nixos/lib/eval-config.nix2
-rw-r--r--nixos/lib/make-disk-image.nix88
-rw-r--r--nixos/lib/make-options-doc/default.nix8
-rw-r--r--nixos/lib/make-options-doc/options-to-docbook.xsl46
-rw-r--r--nixos/lib/make-options-doc/sortXML.py28
-rw-r--r--nixos/lib/make-zfs-image.nix2
-rw-r--r--nixos/lib/qemu-common.nix (renamed from nixos/lib/qemu-flags.nix)12
-rwxr-xr-xnixos/lib/test-driver/test-driver.py876
-rw-r--r--nixos/lib/testing-python.nix153
-rw-r--r--nixos/lib/utils.nix2
-rw-r--r--nixos/maintainers/scripts/azure-new/examples/basic/image.nix2
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix11
-rwxr-xr-xnixos/maintainers/scripts/ec2/create-amis.sh128
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-image-inner.nix102
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-image.nix34
-rw-r--r--nixos/maintainers/scripts/lxd/nix.tpl9
-rw-r--r--nixos/modules/config/console.nix6
-rw-r--r--nixos/modules/config/fonts/fontdir.nix3
-rw-r--r--nixos/modules/config/fonts/fonts.nix2
-rw-r--r--nixos/modules/config/i18n.nix2
-rw-r--r--nixos/modules/config/krb5/default.nix16
-rw-r--r--nixos/modules/config/malloc.nix14
-rw-r--r--nixos/modules/config/networking.nix6
-rw-r--r--nixos/modules/config/power-management.nix4
-rw-r--r--nixos/modules/config/pulseaudio.nix10
-rw-r--r--nixos/modules/config/shells-environment.nix8
-rw-r--r--nixos/modules/config/swap.nix19
-rw-r--r--nixos/modules/config/sysctl.nix2
-rw-r--r--nixos/modules/config/system-path.nix6
-rw-r--r--nixos/modules/config/unix-odbc-drivers.nix2
-rw-r--r--nixos/modules/config/users-groups.nix18
-rw-r--r--nixos/modules/config/xdg/mime.nix66
-rw-r--r--nixos/modules/config/xdg/portals/wlr.nix2
-rw-r--r--nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixos/modules/hardware/ckb-next.nix2
-rw-r--r--nixos/modules/hardware/device-tree.nix10
-rw-r--r--nixos/modules/hardware/digitalbitbox.nix2
-rw-r--r--nixos/modules/hardware/flirc.nix12
-rw-r--r--nixos/modules/hardware/gkraken.nix18
-rw-r--r--nixos/modules/hardware/opengl.nix4
-rw-r--r--nixos/modules/hardware/opentabletdriver.nix2
-rw-r--r--nixos/modules/hardware/printers.nix17
-rw-r--r--nixos/modules/hardware/sata.nix2
-rw-r--r--nixos/modules/hardware/video/nvidia.nix12
-rw-r--r--nixos/modules/hardware/video/uvcvideo/default.nix2
-rw-r--r--nixos/modules/i18n/input-method/fcitx.nix2
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix2
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix4
-rw-r--r--nixos/modules/i18n/input-method/kime.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix6
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-pc.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball.nix4
-rw-r--r--nixos/modules/installer/netboot/netboot.nix2
-rw-r--r--nixos/modules/installer/sd-card/sd-image-raspberrypi.nix2
-rw-r--r--nixos/modules/installer/sd-card/sd-image.nix8
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-build-vms/build-vms.nix20
-rw-r--r--nixos/modules/installer/tools/nixos-enter.sh31
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl8
-rw-r--r--nixos/modules/installer/tools/tools.nix15
-rw-r--r--nixos/modules/misc/documentation.nix20
-rw-r--r--nixos/modules/misc/ids.nix88
-rw-r--r--nixos/modules/misc/locate.nix25
-rw-r--r--nixos/modules/misc/nixpkgs.nix18
-rw-r--r--nixos/modules/module-list.nix47
-rw-r--r--nixos/modules/profiles/base.nix1
-rw-r--r--nixos/modules/profiles/minimal.nix2
-rw-r--r--nixos/modules/programs/atop.nix15
-rw-r--r--nixos/modules/programs/bandwhich.nix4
-rw-r--r--nixos/modules/programs/captive-browser.nix6
-rw-r--r--nixos/modules/programs/ccache.nix2
-rw-r--r--nixos/modules/programs/chromium.nix4
-rw-r--r--nixos/modules/programs/cnping.nix21
-rw-r--r--nixos/modules/programs/command-not-found/command-not-found.pl9
-rw-r--r--nixos/modules/programs/digitalbitbox/default.nix2
-rw-r--r--nixos/modules/programs/dmrconfig.nix2
-rw-r--r--nixos/modules/programs/environment.nix6
-rw-r--r--nixos/modules/programs/evince.nix17
-rw-r--r--nixos/modules/programs/extra-container.nix17
-rw-r--r--nixos/modules/programs/feedbackd.nix1
-rw-r--r--nixos/modules/programs/file-roller.nix17
-rw-r--r--nixos/modules/programs/firejail.nix13
-rw-r--r--nixos/modules/programs/flexoptix-app.nix2
-rw-r--r--nixos/modules/programs/freetds.nix2
-rw-r--r--nixos/modules/programs/gamemode.nix4
-rw-r--r--nixos/modules/programs/git.nix69
-rw-r--r--nixos/modules/programs/gnupg.nix2
-rw-r--r--nixos/modules/programs/htop.nix58
-rw-r--r--nixos/modules/programs/iftop.nix4
-rw-r--r--nixos/modules/programs/iotop.nix4
-rw-r--r--nixos/modules/programs/java.nix2
-rw-r--r--nixos/modules/programs/kbdlight.nix7
-rw-r--r--nixos/modules/programs/kdeconnect.nix4
-rw-r--r--nixos/modules/programs/less.nix15
-rw-r--r--nixos/modules/programs/liboping.nix4
-rw-r--r--nixos/modules/programs/mosh.nix2
-rw-r--r--nixos/modules/programs/msmtp.nix2
-rw-r--r--nixos/modules/programs/mtr.nix5
-rw-r--r--nixos/modules/programs/neovim.nix44
-rw-r--r--nixos/modules/programs/noisetorch.nix5
-rw-r--r--nixos/modules/programs/npm.nix5
-rw-r--r--nixos/modules/programs/pantheon-tweaks.nix19
-rw-r--r--nixos/modules/programs/plotinus.nix2
-rw-r--r--nixos/modules/programs/proxychains.nix2
-rw-r--r--nixos/modules/programs/shadow.nix23
-rw-r--r--nixos/modules/programs/singularity.nix7
-rw-r--r--nixos/modules/programs/slock.nix7
-rw-r--r--nixos/modules/programs/spacefm.nix12
-rw-r--r--nixos/modules/programs/ssh.nix7
-rw-r--r--nixos/modules/programs/ssmtp.nix4
-rw-r--r--nixos/modules/programs/sway.nix4
-rw-r--r--nixos/modules/programs/traceroute.nix4
-rw-r--r--nixos/modules/programs/tsm-client.nix8
-rw-r--r--nixos/modules/programs/udevil.nix7
-rw-r--r--nixos/modules/programs/vim.nix4
-rw-r--r--nixos/modules/programs/wavemon.nix4
-rw-r--r--nixos/modules/programs/weylus.nix47
-rw-r--r--nixos/modules/programs/wireshark.nix2
-rw-r--r--nixos/modules/programs/wshowkeys.nix7
-rw-r--r--nixos/modules/programs/xonsh.nix3
-rw-r--r--nixos/modules/programs/xss-lock.nix3
-rw-r--r--nixos/modules/programs/xwayland.nix9
-rw-r--r--nixos/modules/programs/yabar.nix3
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.nix2
-rw-r--r--nixos/modules/programs/zsh/zsh-autoenv.nix2
-rw-r--r--nixos/modules/programs/zsh/zsh-autosuggestions.nix2
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix4
-rw-r--r--nixos/modules/programs/zsh/zsh.nix17
-rw-r--r--nixos/modules/rename.nix4
-rw-r--r--nixos/modules/security/acme.nix14
-rw-r--r--nixos/modules/security/ca.nix23
-rw-r--r--nixos/modules/security/chromium-suid-sandbox.nix7
-rw-r--r--nixos/modules/security/dhparams.nix2
-rw-r--r--nixos/modules/security/doas.nix13
-rw-r--r--nixos/modules/security/duosec.nix7
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix28
-rw-r--r--nixos/modules/security/pam.nix381
-rw-r--r--nixos/modules/security/pam_mount.nix4
-rw-r--r--nixos/modules/security/pam_usb.nix14
-rw-r--r--nixos/modules/security/polkit.nix14
-rw-r--r--nixos/modules/security/rtkit.nix5
-rw-r--r--nixos/modules/security/sudo.nix4
-rw-r--r--nixos/modules/security/systemd-confinement.nix4
-rw-r--r--nixos/modules/security/tpm2.nix10
-rw-r--r--nixos/modules/security/wrappers/default.nix284
-rw-r--r--nixos/modules/services/admin/meshcentral.nix2
-rw-r--r--nixos/modules/services/admin/oxidized.nix4
-rw-r--r--nixos/modules/services/amqp/activemq/default.nix3
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix31
-rw-r--r--nixos/modules/services/audio/botamusique.nix1
-rw-r--r--nixos/modules/services/audio/hqplayerd.nix2
-rw-r--r--nixos/modules/services/audio/jack.nix6
-rw-r--r--nixos/modules/services/audio/liquidsoap.nix6
-rw-r--r--nixos/modules/services/audio/mopidy.nix2
-rw-r--r--nixos/modules/services/audio/mpd.nix6
-rw-r--r--nixos/modules/services/audio/roon-server.nix2
-rw-r--r--nixos/modules/services/audio/slimserver.nix2
-rw-r--r--nixos/modules/services/audio/snapserver.nix6
-rw-r--r--nixos/modules/services/audio/spotifyd.nix27
-rw-r--r--nixos/modules/services/audio/ympd.nix1
-rw-r--r--nixos/modules/services/backup/automysqlbackup.nix5
-rw-r--r--nixos/modules/services/backup/borgbackup.nix57
-rw-r--r--nixos/modules/services/backup/btrbk.nix2
-rw-r--r--nixos/modules/services/backup/postgresql-backup.nix2
-rw-r--r--nixos/modules/services/backup/postgresql-wal-receiver.nix10
-rw-r--r--nixos/modules/services/backup/restic-rest-server.nix2
-rw-r--r--nixos/modules/services/backup/restic.nix18
-rw-r--r--nixos/modules/services/backup/sanoid.nix9
-rw-r--r--nixos/modules/services/backup/syncoid.nix81
-rw-r--r--nixos/modules/services/backup/tarsnap.nix8
-rw-r--r--nixos/modules/services/backup/znapzend.nix14
-rw-r--r--nixos/modules/services/blockchain/ethereum/geth.nix1
-rw-r--r--nixos/modules/services/cluster/hadoop/conf.nix35
-rw-r--r--nixos/modules/services/cluster/hadoop/default.nix129
-rw-r--r--nixos/modules/services/cluster/hadoop/hdfs.nix186
-rw-r--r--nixos/modules/services/cluster/hadoop/yarn.nix102
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/addon-manager.nix4
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix31
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix45
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix6
-rw-r--r--nixos/modules/services/cluster/spark/default.nix162
-rw-r--r--nixos/modules/services/computing/boinc/client.nix4
-rw-r--r--nixos/modules/services/computing/foldingathome/client.nix2
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix8
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix11
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix6
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agents.nix7
-rw-r--r--nixos/modules/services/continuous-integration/github-runner.nix3
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix12
-rw-r--r--nixos/modules/services/continuous-integration/gocd-agent/default.nix2
-rw-r--r--nixos/modules/services/continuous-integration/gocd-server/default.nix2
-rw-r--r--nixos/modules/services/continuous-integration/hail.nix2
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix92
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix6
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix4
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/default.nix6
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/job-builder.nix4
-rw-r--r--nixos/modules/services/databases/aerospike.nix2
-rw-r--r--nixos/modules/services/databases/cassandra.nix10
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix2
-rw-r--r--nixos/modules/services/databases/couchdb.nix3
-rw-r--r--nixos/modules/services/databases/firebird.nix8
-rw-r--r--nixos/modules/services/databases/hbase.nix47
-rw-r--r--nixos/modules/services/databases/influxdb.nix8
-rw-r--r--nixos/modules/services/databases/influxdb2.nix4
-rw-r--r--nixos/modules/services/databases/memcached.nix2
-rw-r--r--nixos/modules/services/databases/monetdb.nix2
-rw-r--r--nixos/modules/services/databases/mongodb.nix6
-rw-r--r--nixos/modules/services/databases/mysql.nix16
-rw-r--r--nixos/modules/services/databases/neo4j.nix6
-rw-r--r--nixos/modules/services/databases/openldap.nix13
-rw-r--r--nixos/modules/services/databases/opentsdb.nix3
-rw-r--r--nixos/modules/services/databases/pgmanage.nix2
-rw-r--r--nixos/modules/services/databases/postgresql.nix12
-rw-r--r--nixos/modules/services/databases/redis.nix6
-rw-r--r--nixos/modules/services/databases/riak.nix3
-rw-r--r--nixos/modules/services/databases/victoriametrics.nix2
-rw-r--r--nixos/modules/services/desktops/cpupower-gui.nix56
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix4
-rw-r--r--nixos/modules/services/desktops/gnome/evolution-data-server.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-keyring.nix4
-rw-r--r--nixos/modules/services/desktops/gsignond.nix2
-rw-r--r--nixos/modules/services/desktops/gvfs.nix1
-rw-r--r--nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json243
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json (renamed from nixos/modules/services/desktops/pipewire/client-rt.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/client.conf.json (renamed from nixos/modules/services/desktops/pipewire/client.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/jack.conf.json (renamed from nixos/modules/services/desktops/pipewire/jack.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json (renamed from nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json (renamed from nixos/modules/services/desktops/pipewire/pipewire.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json (renamed from nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json (renamed from nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json (renamed from nixos/modules/services/desktops/pipewire/media-session.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json (renamed from nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json)0
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-media-session.nix28
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix14
-rw-r--r--nixos/modules/services/development/blackfire.nix13
-rw-r--r--nixos/modules/services/development/blackfire.xml3
-rw-r--r--nixos/modules/services/development/distccd.nix2
-rw-r--r--nixos/modules/services/development/hoogle.nix6
-rw-r--r--nixos/modules/services/development/jupyter/default.nix8
-rw-r--r--nixos/modules/services/development/jupyter/kernel-options.nix8
-rw-r--r--nixos/modules/services/development/jupyterhub/default.nix28
-rw-r--r--nixos/modules/services/development/lorri.nix3
-rw-r--r--nixos/modules/services/display-managers/greetd.nix13
-rw-r--r--nixos/modules/services/editors/emacs.nix2
-rw-r--r--nixos/modules/services/editors/infinoted.nix2
-rw-r--r--nixos/modules/services/finance/odoo.nix122
-rw-r--r--nixos/modules/services/games/crossfire-server.nix179
-rw-r--r--nixos/modules/services/games/deliantra-server.nix172
-rw-r--r--nixos/modules/services/games/factorio.nix6
-rw-r--r--nixos/modules/services/games/minecraft-server.nix12
-rw-r--r--nixos/modules/services/hardware/acpid.nix2
-rw-r--r--nixos/modules/services/hardware/actkbd.nix2
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix6
-rw-r--r--nixos/modules/services/hardware/fancontrol.nix1
-rw-r--r--nixos/modules/services/hardware/freefall.nix2
-rw-r--r--nixos/modules/services/hardware/fwupd.nix3
-rw-r--r--nixos/modules/services/hardware/interception-tools.nix1
-rw-r--r--nixos/modules/services/hardware/joycond.nix40
-rw-r--r--nixos/modules/services/hardware/lirc.nix2
-rw-r--r--nixos/modules/services/hardware/pcscd.nix4
-rw-r--r--nixos/modules/services/hardware/power-profiles-daemon.nix2
-rw-r--r--nixos/modules/services/hardware/rasdaemon.nix171
-rw-r--r--nixos/modules/services/hardware/sane.nix3
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4.nix8
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan5.nix8
-rw-r--r--nixos/modules/services/hardware/tcsd.nix6
-rw-r--r--nixos/modules/services/hardware/thermald.nix2
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix2
-rw-r--r--nixos/modules/services/hardware/undervolt.nix2
-rw-r--r--nixos/modules/services/hardware/upower.nix3
-rw-r--r--nixos/modules/services/hardware/vdr.nix4
-rw-r--r--nixos/modules/services/logging/SystemdJournal2Gelf.nix1
-rw-r--r--nixos/modules/services/logging/awstats.nix8
-rw-r--r--nixos/modules/services/logging/fluentd.nix2
-rw-r--r--nixos/modules/services/logging/graylog.nix9
-rw-r--r--nixos/modules/services/logging/journalbeat.nix4
-rw-r--r--nixos/modules/services/logging/logcheck.nix8
-rw-r--r--nixos/modules/services/logging/logrotate.nix2
-rw-r--r--nixos/modules/services/logging/logstash.nix32
-rw-r--r--nixos/modules/services/logging/promtail.nix4
-rw-r--r--nixos/modules/services/logging/syslog-ng.nix4
-rw-r--r--nixos/modules/services/mail/davmail.nix2
-rw-r--r--nixos/modules/services/mail/dovecot.nix7
-rw-r--r--nixos/modules/services/mail/exim.nix11
-rw-r--r--nixos/modules/services/mail/mail.nix3
-rw-r--r--nixos/modules/services/mail/mailman.nix4
-rw-r--r--nixos/modules/services/mail/offlineimap.nix4
-rw-r--r--nixos/modules/services/mail/opensmtpd.nix7
-rw-r--r--nixos/modules/services/mail/postfix.nix11
-rw-r--r--nixos/modules/services/mail/roundcube.nix7
-rw-r--r--nixos/modules/services/mail/rspamd.nix10
-rw-r--r--nixos/modules/services/mail/sympa.nix8
-rw-r--r--nixos/modules/services/matrix/mjolnir.nix242
-rw-r--r--nixos/modules/services/matrix/mjolnir.xml134
-rw-r--r--nixos/modules/services/matrix/pantalaimon-options.nix70
-rw-r--r--nixos/modules/services/matrix/pantalaimon.nix70
-rw-r--r--nixos/modules/services/misc/airsonic.nix8
-rw-r--r--nixos/modules/services/misc/ananicy.nix107
-rw-r--r--nixos/modules/services/misc/ankisyncd.nix2
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix8
-rw-r--r--nixos/modules/services/misc/autofs.nix2
-rw-r--r--nixos/modules/services/misc/bees.nix4
-rw-r--r--nixos/modules/services/misc/cfdyndns.nix2
-rw-r--r--nixos/modules/services/misc/cgminer.nix10
-rw-r--r--nixos/modules/services/misc/clipcat.nix2
-rw-r--r--nixos/modules/services/misc/clipmenu.nix2
-rwxr-xr-xnixos/modules/services/misc/confd.nix2
-rw-r--r--nixos/modules/services/misc/dictd.nix4
-rw-r--r--nixos/modules/services/misc/disnix.nix3
-rw-r--r--nixos/modules/services/misc/docker-registry.nix2
-rw-r--r--nixos/modules/services/misc/dwm-status.nix4
-rw-r--r--nixos/modules/services/misc/etcd.nix6
-rw-r--r--nixos/modules/services/misc/etebase-server.nix6
-rw-r--r--nixos/modules/services/misc/felix.nix2
-rw-r--r--nixos/modules/services/misc/freeswitch.nix9
-rw-r--r--nixos/modules/services/misc/gammu-smsd.nix4
-rw-r--r--nixos/modules/services/misc/gitea.nix15
-rw-r--r--nixos/modules/services/misc/gitit.nix52
-rw-r--r--nixos/modules/services/misc/gitlab.nix67
-rw-r--r--nixos/modules/services/misc/gitolite.nix12
-rw-r--r--nixos/modules/services/misc/gpsd.nix1
-rw-r--r--nixos/modules/services/misc/greenclip.nix2
-rw-r--r--nixos/modules/services/misc/home-assistant.nix10
-rw-r--r--nixos/modules/services/misc/ihaskell.nix7
-rw-r--r--nixos/modules/services/misc/jackett.nix2
-rw-r--r--nixos/modules/services/misc/jellyfin.nix2
-rw-r--r--nixos/modules/services/misc/klipper.nix1
-rw-r--r--nixos/modules/services/misc/lidarr.nix2
-rw-r--r--nixos/modules/services/misc/mame.nix4
-rw-r--r--nixos/modules/services/misc/matrix-appservice-discord.nix2
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix14
-rw-r--r--nixos/modules/services/misc/mautrix-facebook.nix195
-rw-r--r--nixos/modules/services/misc/mautrix-telegram.nix2
-rw-r--r--nixos/modules/services/misc/mbpfan.nix2
-rw-r--r--nixos/modules/services/misc/mediatomb.nix7
-rw-r--r--nixos/modules/services/misc/mx-puppet-discord.nix2
-rw-r--r--nixos/modules/services/misc/n8n.nix2
-rw-r--r--nixos/modules/services/misc/nitter.nix2
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix85
-rw-r--r--nixos/modules/services/misc/nix-ssh-serve.nix4
-rw-r--r--nixos/modules/services/misc/nzbhydra2.nix2
-rw-r--r--nixos/modules/services/misc/octoprint.nix4
-rw-r--r--nixos/modules/services/misc/owncast.nix98
-rw-r--r--nixos/modules/services/misc/paperless-ng.nix8
-rw-r--r--nixos/modules/services/misc/plex.nix26
-rw-r--r--nixos/modules/services/misc/prowlarr.nix41
-rw-r--r--nixos/modules/services/misc/redmine.nix17
-rw-r--r--nixos/modules/services/misc/ripple-data-api.nix4
-rw-r--r--nixos/modules/services/misc/rippled.nix10
-rw-r--r--nixos/modules/services/misc/safeeyes.nix4
-rw-r--r--nixos/modules/services/misc/sickbeard.nix5
-rw-r--r--nixos/modules/services/misc/signald.nix105
-rw-r--r--nixos/modules/services/misc/snapper.nix61
-rw-r--r--nixos/modules/services/misc/sourcehut/builds.nix2
-rw-r--r--nixos/modules/services/misc/sourcehut/git.nix3
-rw-r--r--nixos/modules/services/misc/ssm-agent.nix2
-rw-r--r--nixos/modules/services/misc/subsonic.nix3
-rw-r--r--nixos/modules/services/misc/tautulli.nix2
-rw-r--r--nixos/modules/services/misc/tp-auto-kbbl.nix58
-rw-r--r--nixos/modules/services/misc/uhub.nix2
-rw-r--r--nixos/modules/services/misc/weechat.nix14
-rw-r--r--nixos/modules/services/misc/xmr-stak.nix2
-rw-r--r--nixos/modules/services/misc/xmrig.nix75
-rw-r--r--nixos/modules/services/misc/zigbee2mqtt.nix4
-rw-r--r--nixos/modules/services/misc/zoneminder.nix2
-rw-r--r--nixos/modules/services/misc/zookeeper.nix6
-rw-r--r--nixos/modules/services/monitoring/alerta.nix4
-rw-r--r--nixos/modules/services/monitoring/arbtt.nix3
-rw-r--r--nixos/modules/services/monitoring/bosun.nix3
-rw-r--r--nixos/modules/services/monitoring/cadvisor.nix2
-rw-r--r--nixos/modules/services/monitoring/collectd.nix6
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix16
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix5
-rw-r--r--nixos/modules/services/monitoring/grafana.nix13
-rw-r--r--nixos/modules/services/monitoring/graphite.nix7
-rw-r--r--nixos/modules/services/monitoring/heapster.nix6
-rw-r--r--nixos/modules/services/monitoring/incron.nix9
-rw-r--r--nixos/modules/services/monitoring/kapacitor.nix5
-rw-r--r--nixos/modules/services/monitoring/loki.nix4
-rw-r--r--nixos/modules/services/monitoring/mackerel-agent.nix1
-rw-r--r--nixos/modules/services/monitoring/metricbeat.nix7
-rw-r--r--nixos/modules/services/monitoring/munin.nix12
-rw-r--r--nixos/modules/services/monitoring/nagios.nix10
-rw-r--r--nixos/modules/services/monitoring/netdata.nix84
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.md113
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix537
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.xml125
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix1522
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix29
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bird.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fastly.nix41
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/flow.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/kea.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/knot.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mail.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginx.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/node.nix13
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pihole.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postgres.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/process.nix14
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix5
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/script.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix16
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/sql.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/systemd.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unbound.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix5
-rw-r--r--nixos/modules/services/monitoring/prometheus/pushgateway.nix2
-rw-r--r--nixos/modules/services/monitoring/scollector.nix5
-rw-r--r--nixos/modules/services/monitoring/teamviewer.nix9
-rw-r--r--nixos/modules/services/monitoring/telegraf.nix4
-rw-r--r--nixos/modules/services/monitoring/thanos.nix8
-rw-r--r--nixos/modules/services/monitoring/tuptime.nix1
-rw-r--r--nixos/modules/services/monitoring/unifi-poller.nix84
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix10
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix17
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix15
-rw-r--r--nixos/modules/services/network-filesystems/ceph.nix32
-rw-r--r--nixos/modules/services/network-filesystems/glusterfs.nix3
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix22
-rw-r--r--nixos/modules/services/network-filesystems/litestream/default.nix2
-rw-r--r--nixos/modules/services/network-filesystems/openafs/client.nix15
-rw-r--r--nixos/modules/services/network-filesystems/openafs/server.nix4
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/client.nix1
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/server.nix18
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix22
-rw-r--r--nixos/modules/services/network-filesystems/tahoe.nix6
-rw-r--r--nixos/modules/services/network-filesystems/webdav.nix107
-rw-r--r--nixos/modules/services/network-filesystems/xtreemfs.nix4
-rw-r--r--nixos/modules/services/networking/3proxy.nix19
-rw-r--r--nixos/modules/services/networking/antennas.nix80
-rw-r--r--nixos/modules/services/networking/asterisk.nix4
-rw-r--r--nixos/modules/services/networking/atftpd.nix2
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix6
-rw-r--r--nixos/modules/services/networking/bee.nix4
-rw-r--r--nixos/modules/services/networking/biboumi.nix1
-rw-r--r--nixos/modules/services/networking/bind.nix8
-rw-r--r--nixos/modules/services/networking/bitcoind.nix4
-rw-r--r--nixos/modules/services/networking/bitlbee.nix24
-rw-r--r--nixos/modules/services/networking/blockbook-frontend.nix15
-rw-r--r--nixos/modules/services/networking/cjdns.nix4
-rw-r--r--nixos/modules/services/networking/connman.nix5
-rw-r--r--nixos/modules/services/networking/consul.nix8
-rw-r--r--nixos/modules/services/networking/coredns.nix2
-rw-r--r--nixos/modules/services/networking/corerad.nix6
-rw-r--r--nixos/modules/services/networking/coturn.nix5
-rw-r--r--nixos/modules/services/networking/ddclient.nix61
-rw-r--r--nixos/modules/services/networking/dhcpd.nix4
-rw-r--r--nixos/modules/services/networking/dnscache.nix2
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy2.nix6
-rw-r--r--nixos/modules/services/networking/dnscrypt-wrapper.nix1
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix4
-rw-r--r--nixos/modules/services/networking/doh-proxy-rust.nix2
-rw-r--r--nixos/modules/services/networking/ejabberd.nix4
-rw-r--r--nixos/modules/services/networking/epmd.nix1
-rw-r--r--nixos/modules/services/networking/ferm.nix4
-rw-r--r--nixos/modules/services/networking/firefox/sync-server.nix2
-rw-r--r--nixos/modules/services/networking/firewall.nix6
-rw-r--r--nixos/modules/services/networking/flannel.nix4
-rw-r--r--nixos/modules/services/networking/ghostunnel.nix4
-rw-r--r--nixos/modules/services/networking/git-daemon.nix1
-rw-r--r--nixos/modules/services/networking/globalprotect-vpn.nix2
-rw-r--r--nixos/modules/services/networking/gnunet.nix4
-rw-r--r--nixos/modules/services/networking/gobgpd.nix2
-rw-r--r--nixos/modules/services/networking/hans.nix2
-rw-r--r--nixos/modules/services/networking/hylafax/options.nix8
-rw-r--r--nixos/modules/services/networking/i2pd.nix2
-rw-r--r--nixos/modules/services/networking/icecream/daemon.nix2
-rw-r--r--nixos/modules/services/networking/icecream/scheduler.nix2
-rw-r--r--nixos/modules/services/networking/inspircd.nix4
-rw-r--r--nixos/modules/services/networking/iodine.nix3
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/default.nix4
-rw-r--r--nixos/modules/services/networking/iscsi/initiator.nix2
-rw-r--r--nixos/modules/services/networking/iscsi/root-initiator.nix9
-rw-r--r--nixos/modules/services/networking/jibri/default.nix417
-rw-r--r--nixos/modules/services/networking/jibri/logging.properties-journal32
-rw-r--r--nixos/modules/services/networking/jicofo.nix2
-rw-r--r--nixos/modules/services/networking/jitsi-videobridge.nix6
-rw-r--r--nixos/modules/services/networking/keepalived/vrrp-instance-options.nix4
-rw-r--r--nixos/modules/services/networking/keepalived/vrrp-script-options.nix2
-rw-r--r--nixos/modules/services/networking/kippo.nix117
-rw-r--r--nixos/modules/services/networking/knot.nix2
-rw-r--r--nixos/modules/services/networking/kresd.nix4
-rw-r--r--nixos/modules/services/networking/lambdabot.nix2
-rw-r--r--nixos/modules/services/networking/libreswan.nix4
-rw-r--r--nixos/modules/services/networking/lxd-image-server.nix138
-rw-r--r--nixos/modules/services/networking/minidlna.nix2
-rw-r--r--nixos/modules/services/networking/miredo.nix2
-rw-r--r--nixos/modules/services/networking/morty.nix9
-rw-r--r--nixos/modules/services/networking/mosquitto.md102
-rw-r--r--nixos/modules/services/networking/mosquitto.nix705
-rw-r--r--nixos/modules/services/networking/mosquitto.xml147
-rw-r--r--nixos/modules/services/networking/multipath.nix572
-rw-r--r--nixos/modules/services/networking/murmur.nix2
-rw-r--r--nixos/modules/services/networking/mxisd.nix2
-rw-r--r--nixos/modules/services/networking/nat.nix2
-rw-r--r--nixos/modules/services/networking/nats.nix3
-rw-r--r--nixos/modules/services/networking/ncdns.nix4
-rw-r--r--nixos/modules/services/networking/ndppd.nix2
-rw-r--r--nixos/modules/services/networking/nebula.nix14
-rw-r--r--nixos/modules/services/networking/networkmanager.nix11
-rw-r--r--nixos/modules/services/networking/nftables.nix2
-rw-r--r--nixos/modules/services/networking/ngircd.nix7
-rw-r--r--nixos/modules/services/networking/nixops-dns.nix1
-rw-r--r--nixos/modules/services/networking/nntp-proxy.nix36
-rw-r--r--nixos/modules/services/networking/nomad.nix27
-rw-r--r--nixos/modules/services/networking/nsd.nix6
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix2
-rw-r--r--nixos/modules/services/networking/ntp/ntpd.nix14
-rw-r--r--nixos/modules/services/networking/ntp/openntpd.nix4
-rw-r--r--nixos/modules/services/networking/ofono.nix2
-rw-r--r--nixos/modules/services/networking/onedrive.nix3
-rw-r--r--nixos/modules/services/networking/openvpn.nix2
-rw-r--r--nixos/modules/services/networking/ostinato.nix4
-rw-r--r--nixos/modules/services/networking/pdns-recursor.nix2
-rw-r--r--nixos/modules/services/networking/pleroma.nix3
-rw-r--r--nixos/modules/services/networking/pleroma.xml298
-rw-r--r--nixos/modules/services/networking/pppd.nix2
-rw-r--r--nixos/modules/services/networking/privoxy.nix2
-rw-r--r--nixos/modules/services/networking/prosody.nix4
-rw-r--r--nixos/modules/services/networking/quassel.nix3
-rw-r--r--nixos/modules/services/networking/quorum.nix4
-rw-r--r--nixos/modules/services/networking/radicale.nix13
-rw-r--r--nixos/modules/services/networking/radvd.nix5
-rw-r--r--nixos/modules/services/networking/rdnssd.nix4
-rw-r--r--nixos/modules/services/networking/sabnzbd.nix9
-rw-r--r--nixos/modules/services/networking/seafile.nix290
-rw-r--r--nixos/modules/services/networking/searx.nix6
-rw-r--r--nixos/modules/services/networking/shadowsocks.nix10
-rw-r--r--nixos/modules/services/networking/shellhub-agent.nix2
-rw-r--r--nixos/modules/services/networking/shorewall.nix2
-rw-r--r--nixos/modules/services/networking/shorewall6.nix2
-rw-r--r--nixos/modules/services/networking/shout.nix4
-rw-r--r--nixos/modules/services/networking/skydns.nix2
-rw-r--r--nixos/modules/services/networking/smartdns.nix2
-rw-r--r--nixos/modules/services/networking/smokeping.nix76
-rw-r--r--nixos/modules/services/networking/sniproxy.nix4
-rw-r--r--nixos/modules/services/networking/softether.nix2
-rw-r--r--nixos/modules/services/networking/spacecookie.nix4
-rw-r--r--nixos/modules/services/networking/spiped.nix2
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix7
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/module.nix2
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix10
-rw-r--r--nixos/modules/services/networking/strongswan.nix4
-rw-r--r--nixos/modules/services/networking/stunnel.nix1
-rw-r--r--nixos/modules/services/networking/supplicant.nix4
-rw-r--r--nixos/modules/services/networking/supybot.nix7
-rw-r--r--nixos/modules/services/networking/syncthing.nix10
-rw-r--r--nixos/modules/services/networking/tailscale.nix2
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix22
-rw-r--r--nixos/modules/services/networking/tedicross.nix2
-rw-r--r--nixos/modules/services/networking/thelounge.nix2
-rw-r--r--nixos/modules/services/networking/tinc.nix10
-rw-r--r--nixos/modules/services/networking/tinydns.nix6
-rw-r--r--nixos/modules/services/networking/tox-bootstrapd.nix21
-rw-r--r--nixos/modules/services/networking/toxvpn.nix6
-rw-r--r--nixos/modules/services/networking/trickster.nix2
-rw-r--r--nixos/modules/services/networking/tvheadend.nix4
-rw-r--r--nixos/modules/services/networking/ucarp.nix10
-rw-r--r--nixos/modules/services/networking/unbound.nix4
-rw-r--r--nixos/modules/services/networking/unifi.nix98
-rw-r--r--nixos/modules/services/networking/v2ray.nix18
-rw-r--r--nixos/modules/services/networking/vsftpd.nix6
-rw-r--r--nixos/modules/services/networking/wakeonlan.nix70
-rw-r--r--nixos/modules/services/networking/websockify.nix2
-rw-r--r--nixos/modules/services/networking/wg-quick.nix16
-rw-r--r--nixos/modules/services/networking/wicd.nix40
-rw-r--r--nixos/modules/services/networking/wireguard.nix10
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix114
-rw-r--r--nixos/modules/services/networking/x2goserver.nix17
-rw-r--r--nixos/modules/services/networking/xandikos.nix4
-rw-r--r--nixos/modules/services/networking/xrdp.nix2
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix2
-rw-r--r--nixos/modules/services/networking/zeronet.nix6
-rw-r--r--nixos/modules/services/networking/zerotierone.nix3
-rw-r--r--nixos/modules/services/networking/znc/default.nix6
-rw-r--r--nixos/modules/services/networking/znc/options.nix6
-rw-r--r--nixos/modules/services/printing/cupsd.nix2
-rw-r--r--nixos/modules/services/scheduling/atd.nix4
-rw-r--r--nixos/modules/services/scheduling/cron.nix9
-rw-r--r--nixos/modules/services/scheduling/fcron.nix3
-rw-r--r--nixos/modules/services/search/elasticsearch.nix19
-rw-r--r--nixos/modules/services/search/hound.nix24
-rw-r--r--nixos/modules/services/search/kibana.nix7
-rw-r--r--nixos/modules/services/search/meilisearch.md39
-rw-r--r--nixos/modules/services/search/meilisearch.nix132
-rw-r--r--nixos/modules/services/search/meilisearch.xml85
-rw-r--r--nixos/modules/services/search/solr.nix2
-rw-r--r--nixos/modules/services/security/certmgr.nix4
-rw-r--r--nixos/modules/services/security/cfssl.nix4
-rw-r--r--nixos/modules/services/security/fail2ban.nix10
-rw-r--r--nixos/modules/services/security/fprintd.nix4
-rw-r--r--nixos/modules/services/security/haka.nix2
-rw-r--r--nixos/modules/services/security/hockeypuck.nix4
-rw-r--r--nixos/modules/services/security/nginx-sso.nix4
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix2
-rw-r--r--nixos/modules/services/security/opensnitch.nix24
-rw-r--r--nixos/modules/services/security/physlock.nix10
-rw-r--r--nixos/modules/services/security/privacyidea.nix9
-rw-r--r--nixos/modules/services/security/shibboleth-sp.nix2
-rw-r--r--nixos/modules/services/security/sks.nix4
-rw-r--r--nixos/modules/services/security/step-ca.nix3
-rw-r--r--nixos/modules/services/security/tor.nix7
-rw-r--r--nixos/modules/services/security/usbguard.nix2
-rw-r--r--nixos/modules/services/security/vault.nix2
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix6
-rw-r--r--nixos/modules/services/security/yubikey-agent.nix10
-rw-r--r--nixos/modules/services/system/earlyoom.nix1
-rw-r--r--nixos/modules/services/system/kerberos/heimdal.nix2
-rw-r--r--nixos/modules/services/system/localtime.nix2
-rw-r--r--nixos/modules/services/system/saslauthd.nix2
-rw-r--r--nixos/modules/services/torrent/deluge.nix4
-rw-r--r--nixos/modules/services/torrent/flexget.nix2
-rw-r--r--nixos/modules/services/torrent/magnetico.nix4
-rw-r--r--nixos/modules/services/torrent/opentracker.nix2
-rw-r--r--nixos/modules/services/torrent/peerflix.nix6
-rw-r--r--nixos/modules/services/torrent/rtorrent.nix2
-rw-r--r--nixos/modules/services/torrent/transmission.nix322
-rw-r--r--nixos/modules/services/ttys/getty.nix24
-rw-r--r--nixos/modules/services/video/epgstation/default.nix31
-rw-r--r--nixos/modules/services/video/mirakurun.nix6
-rw-r--r--nixos/modules/services/video/replay-sorcery.nix6
-rw-r--r--nixos/modules/services/video/unifi-video.nix6
-rw-r--r--nixos/modules/services/wayland/cage.nix3
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix4
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix4
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix4
-rw-r--r--nixos/modules/services/web-apps/bookstack.nix22
-rw-r--r--nixos/modules/services/web-apps/code-server.nix139
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix4
-rw-r--r--nixos/modules/services/web-apps/dex.nix115
-rw-r--r--nixos/modules/services/web-apps/discourse.nix57
-rw-r--r--nixos/modules/services/web-apps/documize.nix1
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix604
-rw-r--r--nixos/modules/services/web-apps/engelsystem.nix4
-rw-r--r--nixos/modules/services/web-apps/fluidd.nix10
-rw-r--r--nixos/modules/services/web-apps/galene.nix3
-rw-r--r--nixos/modules/services/web-apps/gerrit.nix3
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix78
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix4
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix2
-rw-r--r--nixos/modules/services/web-apps/ihatemoney/default.nix18
-rw-r--r--nixos/modules/services/web-apps/invidious.nix263
-rw-r--r--nixos/modules/services/web-apps/isso.nix4
-rw-r--r--nixos/modules/services/web-apps/jirafeau.nix11
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix138
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix3
-rw-r--r--nixos/modules/services/web-apps/lemmy.md34
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix236
-rw-r--r--nixos/modules/services/web-apps/lemmy.xml56
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix10
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix56
-rw-r--r--nixos/modules/services/web-apps/matomo.nix38
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix11
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix2
-rw-r--r--nixos/modules/services/web-apps/moinmoin.nix2
-rw-r--r--nixos/modules/services/web-apps/moodle.nix10
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix356
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml6
-rw-r--r--nixos/modules/services/web-apps/nexus.nix22
-rw-r--r--nixos/modules/services/web-apps/node-red.nix7
-rw-r--r--nixos/modules/services/web-apps/openwebrx.nix33
-rw-r--r--nixos/modules/services/web-apps/peertube.nix447
-rw-r--r--nixos/modules/services/web-apps/pgpkeyserver-lite.nix2
-rw-r--r--nixos/modules/services/web-apps/pict-rs.md88
-rw-r--r--nixos/modules/services/web-apps/pict-rs.nix50
-rw-r--r--nixos/modules/services/web-apps/pict-rs.xml162
-rw-r--r--nixos/modules/services/web-apps/plantuml-server.nix4
-rw-r--r--nixos/modules/services/web-apps/plausible.nix92
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix3
-rw-r--r--nixos/modules/services/web-apps/rss-bridge.nix4
-rw-r--r--nixos/modules/services/web-apps/selfoss.nix1
-rw-r--r--nixos/modules/services/web-apps/shiori.nix2
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix2
-rw-r--r--nixos/modules/services/web-apps/vikunja.nix6
-rw-r--r--nixos/modules/services/web-apps/whitebophir.nix2
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix100
-rw-r--r--nixos/modules/services/web-apps/youtrack.nix4
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix9
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix19
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/vhost-options.nix4
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix5
-rw-r--r--nixos/modules/services/web-servers/lighttpd/cgit.nix12
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix14
-rw-r--r--nixos/modules/services/web-servers/minio.nix2
-rw-r--r--nixos/modules/services/web-servers/molly-brown.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix14
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix6
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix10
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix8
-rw-r--r--nixos/modules/services/web-servers/traefik.nix6
-rw-r--r--nixos/modules/services/web-servers/trafficserver/default.nix (renamed from nixos/modules/services/web-servers/trafficserver.nix)56
-rw-r--r--nixos/modules/services/web-servers/trafficserver/ip_allow.json36
-rw-r--r--nixos/modules/services/web-servers/trafficserver/logging.json37
-rw-r--r--nixos/modules/services/web-servers/ttyd.nix2
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix4
-rw-r--r--nixos/modules/services/web-servers/uwsgi.nix4
-rw-r--r--nixos/modules/services/web-servers/varnish/default.nix15
-rw-r--r--nixos/modules/services/web-servers/zope2.nix8
-rw-r--r--nixos/modules/services/x11/clight.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/cde.nix7
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix21
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix30
-rw-r--r--nixos/modules/services/x11/desktop-managers/kodi.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/lumina.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix39
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.xml4
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix273
-rw-r--r--nixos/modules/services/x11/desktop-managers/surf-display.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/xterm.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix12
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix64
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix6
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix6
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix17
-rw-r--r--nixos/modules/services/x11/display-managers/startx.nix3
-rw-r--r--nixos/modules/services/x11/display-managers/sx.nix7
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix57
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix24
-rw-r--r--nixos/modules/services/x11/imwheel.nix20
-rw-r--r--nixos/modules/services/x11/picom.nix2
-rw-r--r--nixos/modules/services/x11/redshift.nix2
-rw-r--r--nixos/modules/services/x11/touchegg.nix38
-rw-r--r--nixos/modules/services/x11/unclutter-xfixes.nix2
-rw-r--r--nixos/modules/services/x11/unclutter.nix2
-rw-r--r--nixos/modules/services/x11/urxvtd.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/awesome.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/bspwm.nix12
-rw-r--r--nixos/modules/services/x11/window-managers/clfswm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/exwm.nix7
-rw-r--r--nixos/modules/services/x11/window-managers/herbstluftwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix6
-rw-r--r--nixos/modules/services/x11/window-managers/wmderland.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix10
-rw-r--r--nixos/modules/services/x11/xautolock.nix7
-rw-r--r--nixos/modules/services/x11/xserver.nix32
-rw-r--r--nixos/modules/system/activation/activation-script.nix23
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl278
-rw-r--r--nixos/modules/system/activation/top-level.nix21
-rw-r--r--nixos/modules/system/boot/binfmt.nix1
-rw-r--r--nixos/modules/system/boot/initrd-openvpn.nix2
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix2
-rw-r--r--nixos/modules/system/boot/kernel.nix19
-rw-r--r--nixos/modules/system/boot/kernel_config.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix12
-rw-r--r--nixos/modules/system/boot/loader/grub/ipxe.nix2
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py86
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix18
-rw-r--r--nixos/modules/system/boot/luksroot.nix3
-rw-r--r--nixos/modules/system/boot/networkd.nix95
-rw-r--r--nixos/modules/system/boot/plymouth.nix3
-rw-r--r--nixos/modules/system/boot/stage-1.nix55
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh8
-rw-r--r--nixos/modules/system/boot/systemd-lib.nix2
-rw-r--r--nixos/modules/system/boot/systemd.nix30
-rw-r--r--nixos/modules/system/boot/tmp.nix17
-rw-r--r--nixos/modules/system/etc/etc.nix14
-rw-r--r--nixos/modules/system/etc/setup-etc.pl6
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix1
-rw-r--r--nixos/modules/tasks/filesystems.nix2
-rw-r--r--nixos/modules/tasks/filesystems/ecryptfs.nix14
-rw-r--r--nixos/modules/tasks/filesystems/nfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix11
-rw-r--r--nixos/modules/tasks/lvm.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix43
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix32
-rw-r--r--nixos/modules/tasks/network-interfaces.nix151
-rw-r--r--nixos/modules/testing/test-instrumentation.nix17
-rw-r--r--nixos/modules/virtualisation/anbox.nix2
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix2
-rw-r--r--nixos/modules/virtualisation/containerd.nix7
-rw-r--r--nixos/modules/virtualisation/containers.nix8
-rw-r--r--nixos/modules/virtualisation/cri-o.nix8
-rw-r--r--nixos/modules/virtualisation/digital-ocean-init.nix2
-rw-r--r--nixos/modules/virtualisation/docker.nix3
-rw-r--r--nixos/modules/virtualisation/ecs-agent.nix2
-rw-r--r--nixos/modules/virtualisation/hyperv-guest.nix2
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix229
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix174
-rw-r--r--nixos/modules/virtualisation/lxd.nix10
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix6
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix16
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix2
-rw-r--r--nixos/modules/virtualisation/parallels-guest.nix3
-rw-r--r--nixos/modules/virtualisation/podman.nix2
-rw-r--r--nixos/modules/virtualisation/proxmox-image.nix169
-rw-r--r--nixos/modules/virtualisation/qemu-guest-agent.nix3
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix388
-rw-r--r--nixos/modules/virtualisation/railcar.nix7
-rw-r--r--nixos/modules/virtualisation/spice-usb-redirection.nix6
-rw-r--r--nixos/modules/virtualisation/virtualbox-guest.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix12
-rw-r--r--nixos/modules/virtualisation/vmware-guest.nix24
-rw-r--r--nixos/modules/virtualisation/waydroid.nix66
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix8
-rw-r--r--nixos/release.nix31
-rw-r--r--nixos/tests/airsonic.nix4
-rw-r--r--nixos/tests/all-tests.nix51
-rw-r--r--nixos/tests/ammonite.nix20
-rw-r--r--nixos/tests/atop.nix2
-rw-r--r--nixos/tests/awscli.nix17
-rw-r--r--nixos/tests/bat.nix12
-rw-r--r--nixos/tests/bittorrent.nix2
-rw-r--r--nixos/tests/boot.nix8
-rw-r--r--nixos/tests/borgbackup.nix33
-rw-r--r--nixos/tests/cage.nix1
-rw-r--r--nixos/tests/cagebreak.nix1
-rw-r--r--nixos/tests/calibre-web.nix10
-rw-r--r--nixos/tests/cassandra.nix1
-rw-r--r--nixos/tests/ceph-multi-node.nix1
-rw-r--r--nixos/tests/ceph-single-node-bluestore.nix1
-rw-r--r--nixos/tests/ceph-single-node.nix1
-rw-r--r--nixos/tests/chromium.nix8
-rw-r--r--nixos/tests/cifs-utils.nix12
-rw-r--r--nixos/tests/cntr.nix2
-rw-r--r--nixos/tests/common/wayland-cage.nix13
-rw-r--r--nixos/tests/containers-bridge.nix3
-rw-r--r--nixos/tests/containers-ephemeral.nix1
-rw-r--r--nixos/tests/containers-extra_veth.nix3
-rw-r--r--nixos/tests/containers-hosts.nix1
-rw-r--r--nixos/tests/containers-imperative.nix5
-rw-r--r--nixos/tests/containers-ip.nix3
-rw-r--r--nixos/tests/containers-macvlans.nix2
-rw-r--r--nixos/tests/containers-physical_interfaces.nix4
-rw-r--r--nixos/tests/containers-portforward.nix3
-rw-r--r--nixos/tests/containers-tmpfs.nix3
-rw-r--r--nixos/tests/croc.nix2
-rw-r--r--nixos/tests/custom-ca.nix30
-rw-r--r--nixos/tests/deluge.nix33
-rw-r--r--nixos/tests/dex-oidc.nix78
-rw-r--r--nixos/tests/disable-installer-tools.nix29
-rw-r--r--nixos/tests/discourse.nix2
-rw-r--r--nixos/tests/doas.nix8
-rw-r--r--nixos/tests/docker-tools.nix20
-rw-r--r--nixos/tests/dokuwiki.nix85
-rw-r--r--nixos/tests/domination.nix26
-rw-r--r--nixos/tests/ec2.nix5
-rw-r--r--nixos/tests/elk.nix55
-rw-r--r--nixos/tests/emacs-daemon.nix2
-rw-r--r--nixos/tests/enlightenment.nix7
-rw-r--r--nixos/tests/etesync-dav.nix2
-rw-r--r--nixos/tests/fcitx/default.nix1
-rw-r--r--nixos/tests/fenics.nix1
-rw-r--r--nixos/tests/firefox.nix7
-rw-r--r--nixos/tests/ft2-clone.nix2
-rw-r--r--nixos/tests/gerrit.nix1
-rw-r--r--nixos/tests/ghostunnel.nix5
-rw-r--r--nixos/tests/gitlab.nix2
-rw-r--r--nixos/tests/gnome-xorg.nix16
-rw-r--r--nixos/tests/gnome.nix16
-rw-r--r--nixos/tests/graphite.nix1
-rw-r--r--nixos/tests/hadoop/hadoop.nix228
-rw-r--r--nixos/tests/hadoop/hdfs.nix41
-rw-r--r--nixos/tests/hadoop/yarn.nix19
-rw-r--r--nixos/tests/handbrake.nix18
-rw-r--r--nixos/tests/hardened.nix5
-rw-r--r--nixos/tests/herbstluftwm.nix1
-rw-r--r--nixos/tests/hibernate.nix9
-rw-r--r--nixos/tests/home-assistant.nix13
-rw-r--r--nixos/tests/ihatemoney/default.nix (renamed from nixos/tests/ihatemoney.nix)32
-rw-r--r--nixos/tests/ihatemoney/rates.json39
-rw-r--r--nixos/tests/ihatemoney/server.crt28
-rw-r--r--nixos/tests/ihatemoney/server.key52
-rw-r--r--nixos/tests/installed-tests/default.nix1
-rw-r--r--nixos/tests/installed-tests/fwupd.nix1
-rw-r--r--nixos/tests/installed-tests/power-profiles-daemon.nix9
-rw-r--r--nixos/tests/installer.nix15
-rw-r--r--nixos/tests/invidious.nix81
-rw-r--r--nixos/tests/iscsi-multipath-root.nix267
-rw-r--r--nixos/tests/jibri.nix69
-rw-r--r--nixos/tests/jitsi-meet.nix1
-rw-r--r--nixos/tests/kafka.nix1
-rw-r--r--nixos/tests/keepassxc.nix2
-rw-r--r--nixos/tests/kerberos/heimdal.nix2
-rw-r--r--nixos/tests/kernel-generic.nix25
-rw-r--r--nixos/tests/kexec.nix11
-rw-r--r--nixos/tests/keycloak.nix1
-rw-r--r--nixos/tests/keymap.nix2
-rw-r--r--nixos/tests/libinput.nix38
-rw-r--r--nixos/tests/libresprite.nix30
-rw-r--r--nixos/tests/libreswan.nix6
-rw-r--r--nixos/tests/lorri/default.nix2
-rw-r--r--nixos/tests/lsd.nix12
-rw-r--r--nixos/tests/lxd-image-server.nix127
-rw-r--r--nixos/tests/lxd-image.nix89
-rw-r--r--nixos/tests/lxd.nix4
-rw-r--r--nixos/tests/magic-wormhole-mailbox-server.nix2
-rw-r--r--nixos/tests/matrix-appservice-irc.nix141
-rw-r--r--nixos/tests/matrix/mjolnir.nix165
-rw-r--r--nixos/tests/matrix/pantalaimon.nix65
-rw-r--r--nixos/tests/meilisearch.nix60
-rw-r--r--nixos/tests/metabase.nix1
-rw-r--r--nixos/tests/minecraft.nix2
-rw-r--r--nixos/tests/minio.nix5
-rw-r--r--nixos/tests/misc.nix5
-rw-r--r--nixos/tests/mosquitto.nix217
-rw-r--r--nixos/tests/mpv.nix4
-rw-r--r--nixos/tests/mumble.nix4
-rw-r--r--nixos/tests/musescore.nix2
-rw-r--r--nixos/tests/mysql/mariadb-galera-mariabackup.nix16
-rw-r--r--nixos/tests/mysql/mariadb-galera-rsync.nix16
-rw-r--r--nixos/tests/mysql/mysql.nix38
-rw-r--r--nixos/tests/networking-proxy.nix1
-rw-r--r--nixos/tests/networking.nix77
-rw-r--r--nixos/tests/nextcloud/basic.nix15
-rw-r--r--nixos/tests/nextcloud/default.nix22
-rw-r--r--nixos/tests/nextcloud/with-mysql-and-memcached.nix18
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix7
-rw-r--r--nixos/tests/nfs/simple.nix2
-rw-r--r--nixos/tests/nginx-etag.nix3
-rw-r--r--nixos/tests/nixops/default.nix114
-rw-r--r--nixos/tests/nixops/legacy/base-configuration.nix31
-rw-r--r--nixos/tests/nixops/legacy/nixops.nix15
-rw-r--r--nixos/tests/odoo.nix27
-rw-r--r--nixos/tests/openarena.nix4
-rw-r--r--nixos/tests/openresty-lua.nix55
-rw-r--r--nixos/tests/opensmtpd-rspamd.nix1
-rw-r--r--nixos/tests/os-prober.nix4
-rw-r--r--nixos/tests/owncast.nix42
-rw-r--r--nixos/tests/pantheon.nix13
-rw-r--r--nixos/tests/paperless-ng.nix1
-rw-r--r--nixos/tests/parsedmarc/default.nix224
-rw-r--r--nixos/tests/pict-rs.nix17
-rw-r--r--nixos/tests/plasma5-systemd-start.nix42
-rw-r--r--nixos/tests/plasma5.nix9
-rw-r--r--nixos/tests/pleroma.nix1
-rw-r--r--nixos/tests/plotinus.nix2
-rw-r--r--nixos/tests/postfixadmin.nix2
-rw-r--r--nixos/tests/printing.nix12
-rw-r--r--nixos/tests/privacyidea.nix1
-rw-r--r--nixos/tests/prometheus-exporters.nix9
-rw-r--r--nixos/tests/prometheus.nix92
-rw-r--r--nixos/tests/prowlarr.nix18
-rw-r--r--nixos/tests/pt2-clone.nix2
-rw-r--r--nixos/tests/rasdaemon.nix34
-rw-r--r--nixos/tests/restart-by-activation-script.nix73
-rw-r--r--nixos/tests/rspamd.nix5
-rw-r--r--nixos/tests/run-in-machine.nix23
-rw-r--r--nixos/tests/samba.nix3
-rw-r--r--nixos/tests/seafile.nix121
-rw-r--r--nixos/tests/service-runner.nix2
-rw-r--r--nixos/tests/shattered-pixel-dungeon.nix2
-rw-r--r--nixos/tests/signal-desktop.nix3
-rw-r--r--nixos/tests/soapui.nix2
-rw-r--r--nixos/tests/spark/default.nix27
-rw-r--r--nixos/tests/spark/spark_sample.py40
-rw-r--r--nixos/tests/spike.nix22
-rw-r--r--nixos/tests/sssd-ldap.nix160
-rw-r--r--nixos/tests/sway.nix1
-rw-r--r--nixos/tests/switch-test.nix386
-rw-r--r--nixos/tests/sympa.nix1
-rw-r--r--nixos/tests/systemd-boot.nix23
-rw-r--r--nixos/tests/systemd-confinement.nix38
-rw-r--r--nixos/tests/systemd-cryptenroll.nix54
-rw-r--r--nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix2
-rw-r--r--nixos/tests/systemd.nix1
-rw-r--r--nixos/tests/tigervnc.nix8
-rw-r--r--nixos/tests/tinydns.nix16
-rw-r--r--nixos/tests/turbovnc-headless-server.nix6
-rw-r--r--nixos/tests/tuxguitar.nix2
-rw-r--r--nixos/tests/ucg.nix18
-rw-r--r--nixos/tests/udisks2.nix2
-rw-r--r--nixos/tests/unbound.nix11
-rw-r--r--nixos/tests/usbguard.nix2
-rw-r--r--nixos/tests/user-activation-scripts.nix33
-rw-r--r--nixos/tests/vault-postgresql.nix1
-rw-r--r--nixos/tests/vault.nix1
-rw-r--r--nixos/tests/vaultwarden.nix1
-rw-r--r--nixos/tests/virtualbox.nix2
-rw-r--r--nixos/tests/vscodium.nix92
-rw-r--r--nixos/tests/wasabibackend.nix2
-rw-r--r--nixos/tests/web-apps/peertube.nix127
-rw-r--r--nixos/tests/wine.nix41
-rw-r--r--nixos/tests/without-nix.nix23
-rw-r--r--nixos/tests/wordpress.nix18
-rw-r--r--nixos/tests/wpa_supplicant.nix81
-rw-r--r--nixos/tests/xfce.nix3
-rw-r--r--nixos/tests/xrdp.nix4
-rw-r--r--nixos/tests/xterm.nix2
-rw-r--r--nixos/tests/yq.nix12
1049 files changed, 24971 insertions, 7212 deletions
diff --git a/nixos/doc/manual/administration/cleaning-store.chapter.md b/nixos/doc/manual/administration/cleaning-store.chapter.md
index fb2090b31d84a..c9140d0869c77 100644
--- a/nixos/doc/manual/administration/cleaning-store.chapter.md
+++ b/nixos/doc/manual/administration/cleaning-store.chapter.md
@@ -58,5 +58,5 @@ a while to finish.
 ## NixOS Boot Entries {#sect-nixos-gc-boot-entries}
 
 If your `/boot` partition runs out of space, after clearing old profiles
-you must rebuild your system with `nixos-rebuild` to update the `/boot`
-partition and clear space.
+you must rebuild your system with `nixos-rebuild boot` or `nixos-rebuild
+switch` to update the `/boot` partition and clear space.
diff --git a/nixos/doc/manual/administration/containers.chapter.md b/nixos/doc/manual/administration/containers.chapter.md
new file mode 100644
index 0000000000000..ea51f91f698fb
--- /dev/null
+++ b/nixos/doc/manual/administration/containers.chapter.md
@@ -0,0 +1,28 @@
+# Container Management {#ch-containers}
+
+NixOS allows you to easily run other NixOS instances as *containers*.
+Containers are a light-weight approach to virtualisation that runs
+software in the container at the same speed as in the host system. NixOS
+containers share the Nix store of the host, making container creation
+very efficient.
+
+::: {.warning}
+Currently, NixOS containers are not perfectly isolated from the host
+system. This means that a user with root access to the container can do
+things that affect the host. So you should not give container root
+access to untrusted users.
+:::
+
+NixOS containers can be created in two ways: imperatively, using the
+command `nixos-container`, and declaratively, by specifying them in your
+`configuration.nix`. The declarative approach implies that containers
+get upgraded along with your host system when you run `nixos-rebuild`,
+which is often not what you want. By contrast, in the imperative
+approach, containers are configured and updated independently from the
+host system.
+
+```{=docbook}
+<xi:include href="imperative-containers.section.xml" />
+<xi:include href="declarative-containers.section.xml" />
+<xi:include href="container-networking.section.xml" />
+```
diff --git a/nixos/doc/manual/administration/containers.xml b/nixos/doc/manual/administration/containers.xml
deleted file mode 100644
index 8e0e300f367b7..0000000000000
--- a/nixos/doc/manual/administration/containers.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-        xmlns:xlink="http://www.w3.org/1999/xlink"
-        xmlns:xi="http://www.w3.org/2001/XInclude"
-        version="5.0"
-        xml:id="ch-containers">
- <title>Container Management</title>
- <para>
-  NixOS allows you to easily run other NixOS instances as
-  <emphasis>containers</emphasis>. Containers are a light-weight approach to
-  virtualisation that runs software in the container at the same speed as in
-  the host system. NixOS containers share the Nix store of the host, making
-  container creation very efficient.
- </para>
- <warning>
-  <para>
-   Currently, NixOS containers are not perfectly isolated from the host system.
-   This means that a user with root access to the container can do things that
-   affect the host. So you should not give container root access to untrusted
-   users.
-  </para>
- </warning>
- <para>
-  NixOS containers can be created in two ways: imperatively, using the command
-  <command>nixos-container</command>, and declaratively, by specifying them in
-  your <filename>configuration.nix</filename>. The declarative approach implies
-  that containers get upgraded along with your host system when you run
-  <command>nixos-rebuild</command>, which is often not what you want. By
-  contrast, in the imperative approach, containers are configured and updated
-  independently from the host system.
- </para>
- <xi:include href="../from_md/administration/imperative-containers.section.xml" />
- <xi:include href="../from_md/administration/declarative-containers.section.xml" />
- <xi:include href="../from_md/administration/container-networking.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/administration/running.xml b/nixos/doc/manual/administration/running.xml
index 24fd864956ffa..d9fcc1aee263f 100644
--- a/nixos/doc/manual/administration/running.xml
+++ b/nixos/doc/manual/administration/running.xml
@@ -16,6 +16,6 @@
  <xi:include href="../from_md/administration/control-groups.chapter.xml" />
  <xi:include href="../from_md/administration/logging.chapter.xml" />
  <xi:include href="../from_md/administration/cleaning-store.chapter.xml" />
- <xi:include href="containers.xml" />
- <xi:include href="troubleshooting.xml" />
+ <xi:include href="../from_md/administration/containers.chapter.xml" />
+ <xi:include href="../from_md/administration/troubleshooting.chapter.xml" />
 </part>
diff --git a/nixos/doc/manual/administration/troubleshooting.chapter.md b/nixos/doc/manual/administration/troubleshooting.chapter.md
new file mode 100644
index 0000000000000..548456eaf6d67
--- /dev/null
+++ b/nixos/doc/manual/administration/troubleshooting.chapter.md
@@ -0,0 +1,12 @@
+# Troubleshooting {#ch-troubleshooting}
+
+This chapter describes solutions to common problems you might encounter
+when you manage your NixOS system.
+
+```{=docbook}
+<xi:include href="boot-problems.section.xml" />
+<xi:include href="maintenance-mode.section.xml" />
+<xi:include href="rollback.section.xml" />
+<xi:include href="store-corruption.section.xml" />
+<xi:include href="network-problems.section.xml" />
+```
diff --git a/nixos/doc/manual/administration/troubleshooting.xml b/nixos/doc/manual/administration/troubleshooting.xml
deleted file mode 100644
index d447b537335bf..0000000000000
--- a/nixos/doc/manual/administration/troubleshooting.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-        xmlns:xlink="http://www.w3.org/1999/xlink"
-        xmlns:xi="http://www.w3.org/2001/XInclude"
-        version="5.0"
-        xml:id="ch-troubleshooting">
- <title>Troubleshooting</title>
- <para>
-  This chapter describes solutions to common problems you might encounter when
-  you manage your NixOS system.
- </para>
- <xi:include href="../from_md/administration/boot-problems.section.xml" />
- <xi:include href="../from_md/administration/maintenance-mode.section.xml" />
- <xi:include href="../from_md/administration/rollback.section.xml" />
- <xi:include href="../from_md/administration/store-corruption.section.xml" />
- <xi:include href="../from_md/administration/network-problems.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/configuration/config-syntax.chapter.md b/nixos/doc/manual/configuration/config-syntax.chapter.md
new file mode 100644
index 0000000000000..56d093c0f6e84
--- /dev/null
+++ b/nixos/doc/manual/configuration/config-syntax.chapter.md
@@ -0,0 +1,19 @@
+# Configuration Syntax {#sec-configuration-syntax}
+
+The NixOS configuration file `/etc/nixos/configuration.nix` is actually
+a *Nix expression*, which is the Nix package manager's purely functional
+language for describing how to build packages and configurations. This
+means you have all the expressive power of that language at your
+disposal, including the ability to abstract over common patterns, which
+is very useful when managing complex systems. The syntax and semantics
+of the Nix language are fully described in the [Nix
+manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions), but
+here we give a short overview of the most important constructs useful in
+NixOS configuration files.
+
+```{=docbook}
+<xi:include href="config-file.section.xml" />
+<xi:include href="abstractions.section.xml" />
+<xi:include href="modularity.section.xml" />
+<xi:include href="summary.section.xml" />
+```
diff --git a/nixos/doc/manual/configuration/config-syntax.xml b/nixos/doc/manual/configuration/config-syntax.xml
deleted file mode 100644
index d1351ff934e58..0000000000000
--- a/nixos/doc/manual/configuration/config-syntax.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="sec-configuration-syntax">
- <title>Configuration Syntax</title>
- <para>
-  The NixOS configuration file
-  <filename>/etc/nixos/configuration.nix</filename> is actually a <emphasis>Nix
-  expression</emphasis>, which is the Nix package manager’s purely functional
-  language for describing how to build packages and configurations. This means
-  you have all the expressive power of that language at your disposal,
-  including the ability to abstract over common patterns, which is very useful
-  when managing complex systems. The syntax and semantics of the Nix language
-  are fully described in the
-  <link
-xlink:href="https://nixos.org/nix/manual/#chap-writing-nix-expressions">Nix
-  manual</link>, but here we give a short overview of the most important
-  constructs useful in NixOS configuration files.
- </para>
- <xi:include href="../from_md/configuration/config-file.section.xml" />
- <xi:include href="../from_md/configuration/abstractions.section.xml" />
- <xi:include href="../from_md/configuration/modularity.section.xml" />
- <xi:include href="../from_md/configuration/summary.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/configuration/configuration.xml b/nixos/doc/manual/configuration/configuration.xml
index 2461a5de73ad1..b04316cfa48e2 100644
--- a/nixos/doc/manual/configuration/configuration.xml
+++ b/nixos/doc/manual/configuration/configuration.xml
@@ -13,19 +13,19 @@
    effect after you run <command>nixos-rebuild</command>.
   </para>
  </partintro>
- <xi:include href="config-syntax.xml" />
- <xi:include href="package-mgmt.xml" />
+ <xi:include href="../from_md/configuration/config-syntax.chapter.xml" />
+ <xi:include href="../from_md/configuration/package-mgmt.chapter.xml" />
  <xi:include href="../from_md/configuration/user-mgmt.chapter.xml" />
- <xi:include href="file-systems.xml" />
+ <xi:include href="../from_md/configuration/file-systems.chapter.xml" />
  <xi:include href="../from_md/configuration/x-windows.chapter.xml" />
  <xi:include href="../from_md/configuration/wayland.chapter.xml" />
  <xi:include href="../from_md/configuration/gpu-accel.chapter.xml" />
  <xi:include href="../from_md/configuration/xfce.chapter.xml" />
- <xi:include href="networking.xml" />
+ <xi:include href="../from_md/configuration/networking.chapter.xml" />
  <xi:include href="../from_md/configuration/linux-kernel.chapter.xml" />
  <xi:include href="../from_md/configuration/subversion.chapter.xml" />
  <xi:include href="../generated/modules.xml" xpointer="xpointer(//section[@id='modules']/*)" />
- <xi:include href="profiles.xml" />
+ <xi:include href="../from_md/configuration/profiles.chapter.xml" />
  <xi:include href="../from_md/configuration/kubernetes.chapter.xml" />
 <!-- Apache; libvirtd virtualisation -->
 </part>
diff --git a/nixos/doc/manual/configuration/declarative-packages.section.md b/nixos/doc/manual/configuration/declarative-packages.section.md
new file mode 100644
index 0000000000000..337cdf8472e40
--- /dev/null
+++ b/nixos/doc/manual/configuration/declarative-packages.section.md
@@ -0,0 +1,46 @@
+# Declarative Package Management {#sec-declarative-package-mgmt}
+
+With declarative package management, you specify which packages you want
+on your system by setting the option
+[](#opt-environment.systemPackages). For instance, adding the
+following line to `configuration.nix` enables the Mozilla Thunderbird
+email application:
+
+```nix
+environment.systemPackages = [ pkgs.thunderbird ];
+```
+
+The effect of this specification is that the Thunderbird package from
+Nixpkgs will be built or downloaded as part of the system when you run
+`nixos-rebuild switch`.
+
+::: {.note}
+Some packages require additional global configuration such as D-Bus or
+systemd service registration so adding them to
+[](#opt-environment.systemPackages) might not be sufficient. You are
+advised to check the [list of options](#ch-options) whether a NixOS
+module for the package does not exist.
+:::
+
+You can get a list of the available packages as follows:
+
+```ShellSession
+$ nix-env -qaP '*' --description
+nixos.firefox   firefox-23.0   Mozilla Firefox - the browser, reloaded
+...
+```
+
+The first column in the output is the *attribute name*, such as
+`nixos.thunderbird`.
+
+Note: the `nixos` prefix tells us that we want to get the package from
+the `nixos` channel and works only in CLI tools. In declarative
+configuration use `pkgs` prefix (variable).
+
+To "uninstall" a package, simply remove it from
+[](#opt-environment.systemPackages) and run `nixos-rebuild switch`.
+
+```{=docbook}
+<xi:include href="customizing-packages.section.xml" />
+<xi:include href="adding-custom-packages.section.xml" />
+```
diff --git a/nixos/doc/manual/configuration/declarative-packages.xml b/nixos/doc/manual/configuration/declarative-packages.xml
deleted file mode 100644
index 8d321929f3f05..0000000000000
--- a/nixos/doc/manual/configuration/declarative-packages.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<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-declarative-package-mgmt">
- <title>Declarative Package Management</title>
-
- <para>
-  With declarative package management, you specify which packages you want on
-  your system by setting the option
-  <xref linkend="opt-environment.systemPackages"/>. For instance, adding the
-  following line to <filename>configuration.nix</filename> enables the Mozilla
-  Thunderbird email application:
-<programlisting>
-<xref linkend="opt-environment.systemPackages"/> = [ pkgs.thunderbird ];
-</programlisting>
-  The effect of this specification is that the Thunderbird package from Nixpkgs
-  will be built or downloaded as part of the system when you run
-  <command>nixos-rebuild switch</command>.
- </para>
-
- <note>
-  <para>
-   Some packages require additional global configuration such as D-Bus or systemd service registration so adding them to <xref linkend="opt-environment.systemPackages"/> might not be sufficient. You are advised to check the <link xlink:href="#ch-options">list of options</link> whether a NixOS module for the package does not exist.
-  </para>
- </note>
-
- <para>
-  You can get a list of the available packages as follows:
-<screen>
-<prompt>$ </prompt>nix-env -qaP '*' --description
-nixos.firefox   firefox-23.0   Mozilla Firefox - the browser, reloaded
-<replaceable>...</replaceable>
-</screen>
-  The first column in the output is the <emphasis>attribute name</emphasis>,
-  such as <literal>nixos.thunderbird</literal>.
- </para>
- <para>
-  Note: the <literal>nixos</literal> prefix tells us that we want to get the
-  package from the <literal>nixos</literal> channel and works only in CLI tools.
-
-  In declarative configuration use <literal>pkgs</literal> prefix (variable).
- </para>
-
- <para>
-  To “uninstall” a package, simply remove it from
-  <xref linkend="opt-environment.systemPackages"/> and run
-  <command>nixos-rebuild switch</command>.
- </para>
-
- <xi:include href="../from_md/configuration/customizing-packages.section.xml" />
-
- <xi:include href="../from_md/configuration/adding-custom-packages.section.xml" />
-</section>
diff --git a/nixos/doc/manual/configuration/file-systems.chapter.md b/nixos/doc/manual/configuration/file-systems.chapter.md
new file mode 100644
index 0000000000000..901e2e4f181b3
--- /dev/null
+++ b/nixos/doc/manual/configuration/file-systems.chapter.md
@@ -0,0 +1,42 @@
+# File Systems {#ch-file-systems}
+
+You can define file systems using the `fileSystems` configuration
+option. For instance, the following definition causes NixOS to mount the
+Ext4 file system on device `/dev/disk/by-label/data` onto the mount
+point `/data`:
+
+```nix
+fileSystems."/data" =
+  { device = "/dev/disk/by-label/data";
+    fsType = "ext4";
+  };
+```
+
+This will create an entry in `/etc/fstab`, which will generate a
+corresponding [systemd.mount](https://www.freedesktop.org/software/systemd/man/systemd.mount.html)
+unit via [systemd-fstab-generator](https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html).
+The filesystem will be mounted automatically unless `"noauto"` is
+present in [options](#opt-fileSystems._name_.options). `"noauto"`
+filesystems can be mounted explicitly using `systemctl` e.g.
+`systemctl start data.mount`. Mount points are created automatically if they don't
+already exist. For `device`, it's best to use the topology-independent
+device aliases in `/dev/disk/by-label` and `/dev/disk/by-uuid`, as these
+don't change if the topology changes (e.g. if a disk is moved to another
+IDE controller).
+
+You can usually omit the file system type (`fsType`), since `mount` can
+usually detect the type and load the necessary kernel module
+automatically. However, if the file system is needed at early boot (in
+the initial ramdisk) and is not `ext2`, `ext3` or `ext4`, then it's best
+to specify `fsType` to ensure that the kernel module is available.
+
+::: {.note}
+System startup will fail if any of the filesystems fails to mount,
+dropping you to the emergency shell. You can make a mount asynchronous
+and non-critical by adding `options = [ "nofail" ];`.
+:::
+
+```{=docbook}
+<xi:include href="luks-file-systems.section.xml" />
+<xi:include href="sshfs-file-systems.section.xml" />
+```
diff --git a/nixos/doc/manual/configuration/file-systems.xml b/nixos/doc/manual/configuration/file-systems.xml
deleted file mode 100644
index 908b5d6c46815..0000000000000
--- a/nixos/doc/manual/configuration/file-systems.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="ch-file-systems">
- <title>File Systems</title>
- <para>
-  You can define file systems using the <option>fileSystems</option>
-  configuration option. For instance, the following definition causes NixOS to
-  mount the Ext4 file system on device
-  <filename>/dev/disk/by-label/data</filename> onto the mount point
-  <filename>/data</filename>:
-<programlisting>
-<xref linkend="opt-fileSystems"/>."/data" =
-  { device = "/dev/disk/by-label/data";
-    fsType = "ext4";
-  };
-</programlisting>
-  This will create an entry in <filename>/etc/fstab</filename>, which will
-  generate a corresponding
-  <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.mount.html">systemd.mount</link>
-  unit via
-  <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html">systemd-fstab-generator</link>.
-  The filesystem will be mounted automatically unless
-  <literal>"noauto"</literal> is present in <link
-  linkend="opt-fileSystems._name_.options">options</link>.
-  <literal>"noauto"</literal> filesystems can be mounted explicitly using
-  <command>systemctl</command> e.g. <command>systemctl start
-  data.mount</command>.
-  Mount points are created automatically if they don’t already exist. For
-  <option><link linkend="opt-fileSystems._name_.device">device</link></option>,
-  it’s best to use the topology-independent device aliases in
-  <filename>/dev/disk/by-label</filename> and
-  <filename>/dev/disk/by-uuid</filename>, as these don’t change if the
-  topology changes (e.g. if a disk is moved to another IDE controller).
- </para>
- <para>
-  You can usually omit the file system type
-  (<option><link linkend="opt-fileSystems._name_.fsType">fsType</link></option>),
-  since <command>mount</command> can usually detect the type and load the
-  necessary kernel module automatically. However, if the file system is needed
-  at early boot (in the initial ramdisk) and is not <literal>ext2</literal>,
-  <literal>ext3</literal> or <literal>ext4</literal>, then it’s best to
-  specify <option>fsType</option> to ensure that the kernel module is
-  available.
- </para>
- <note>
-  <para>
-   System startup will fail if any of the filesystems fails to mount, dropping
-   you to the emergency shell. You can make a mount asynchronous and
-   non-critical by adding
-   <literal><link linkend="opt-fileSystems._name_.options">options</link> = [
-   "nofail" ];</literal>.
-  </para>
- </note>
- <xi:include href="../from_md/configuration/luks-file-systems.section.xml" />
- <xi:include href="../from_md/configuration/sshfs-file-systems.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/configuration/linux-kernel.chapter.md b/nixos/doc/manual/configuration/linux-kernel.chapter.md
index aad6d49c72c32..1d06543d4f1e3 100644
--- a/nixos/doc/manual/configuration/linux-kernel.chapter.md
+++ b/nixos/doc/manual/configuration/linux-kernel.chapter.md
@@ -5,13 +5,18 @@ option `boot.kernelPackages`. For instance, this selects the Linux 3.10
 kernel:
 
 ```nix
-boot.kernelPackages = pkgs.linuxPackages_3_10;
+boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_10;
 ```
 
 Note that this not only replaces the kernel, but also packages that are
 specific to the kernel version, such as the NVIDIA video drivers. This
 ensures that driver packages are consistent with the kernel.
 
+While `pkgs.linuxKernel.packages` contains all available kernel packages,
+you may want to use one of the unversioned `pkgs.linuxPackages_*` aliases
+such as `pkgs.linuxPackages_latest`, that are kept up to date with new
+versions.
+
 The default Linux kernel configuration should be fine for most users.
 You can see the configuration of your current kernel with the following
 command:
@@ -25,14 +30,13 @@ If you want to change the kernel configuration, you can use the
 instance, to enable support for the kernel debugger KGDB:
 
 ```nix
-nixpkgs.config.packageOverrides = pkgs:
-  { linux_3_4 = pkgs.linux_3_4.override {
-      extraConfig =
-        ''
-          KGDB y
-        '';
-    };
+nixpkgs.config.packageOverrides = pkgs: pkgs.lib.recursiveUpdate pkgs {
+  linuxKernel.kernels.linux_5_10 = pkgs.linuxKernel.kernels.linux_5_10.override {
+    extraConfig = ''
+      KGDB y
+    '';
   };
+};
 ```
 
 `extraConfig` takes a list of Linux kernel configuration options, one
@@ -72,16 +76,17 @@ available parameters, run `sysctl -a`.
 
 The first step before compiling the kernel is to generate an appropriate
 `.config` configuration. Either you pass your own config via the
-`configfile` setting of `linuxManualConfig`:
+`configfile` setting of `linuxKernel.manualConfig`:
 
 ```nix
-custom-kernel = super.linuxManualConfig {
-  inherit (super) stdenv hostPlatform;
-  inherit (linux_4_9) src;
-  version = "${linux_4_9.version}-custom";
-
-  configfile = /home/me/my_kernel_config;
-  allowImportFromDerivation = true;
+custom-kernel = let base_kernel = linuxKernel.kernels.linux_4_9;
+  in super.linuxKernel.manualConfig {
+    inherit (super) stdenv hostPlatform;
+    inherit (base_kernel) src;
+    version = "${base_kernel.version}-custom";
+
+    configfile = /home/me/my_kernel_config;
+    allowImportFromDerivation = true;
 };
 ```
 
diff --git a/nixos/doc/manual/configuration/networking.chapter.md b/nixos/doc/manual/configuration/networking.chapter.md
new file mode 100644
index 0000000000000..529dc0610bdaf
--- /dev/null
+++ b/nixos/doc/manual/configuration/networking.chapter.md
@@ -0,0 +1,16 @@
+# Networking {#sec-networking}
+
+This section describes how to configure networking components
+on your NixOS machine.
+
+```{=docbook}
+<xi:include href="network-manager.section.xml" />
+<xi:include href="ssh.section.xml" />
+<xi:include href="ipv4-config.section.xml" />
+<xi:include href="ipv6-config.section.xml" />
+<xi:include href="firewall.section.xml" />
+<xi:include href="wireless.section.xml" />
+<xi:include href="ad-hoc-network-config.section.xml" />
+<xi:include href="renaming-interfaces.section.xml" />
+```
+<!-- TODO: OpenVPN, NAT -->
diff --git a/nixos/doc/manual/configuration/networking.xml b/nixos/doc/manual/configuration/networking.xml
deleted file mode 100644
index 5dd0278569b9a..0000000000000
--- a/nixos/doc/manual/configuration/networking.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="sec-networking">
- <title>Networking</title>
- <para>
-  This section describes how to configure networking components on your NixOS
-  machine.
- </para>
- <xi:include href="../from_md/configuration/network-manager.section.xml" />
- <xi:include href="../from_md/configuration/ssh.section.xml" />
- <xi:include href="../from_md/configuration/ipv4-config.section.xml" />
- <xi:include href="../from_md/configuration/ipv6-config.section.xml" />
- <xi:include href="../from_md/configuration/firewall.section.xml" />
- <xi:include href="../from_md/configuration/wireless.section.xml" />
- <xi:include href="../from_md/configuration/ad-hoc-network-config.section.xml" />
- <xi:include href="../from_md/configuration/renaming-interfaces.section.xml" />
-<!-- TODO: OpenVPN, NAT -->
-</chapter>
diff --git a/nixos/doc/manual/configuration/package-mgmt.chapter.md b/nixos/doc/manual/configuration/package-mgmt.chapter.md
new file mode 100644
index 0000000000000..a6c414be59a97
--- /dev/null
+++ b/nixos/doc/manual/configuration/package-mgmt.chapter.md
@@ -0,0 +1,18 @@
+# Package Management {#sec-package-management}
+
+This section describes how to add additional packages to your system.
+NixOS has two distinct styles of package management:
+
+-   *Declarative*, where you declare what packages you want in your
+    `configuration.nix`. Every time you run `nixos-rebuild`, NixOS will
+    ensure that you get a consistent set of binaries corresponding to
+    your specification.
+
+-   *Ad hoc*, where you install, upgrade and uninstall packages via the
+    `nix-env` command. This style allows mixing packages from different
+    Nixpkgs versions. It's the only choice for non-root users.
+
+```{=docbook}
+<xi:include href="declarative-packages.section.xml" />
+<xi:include href="ad-hoc-packages.section.xml" />
+```
diff --git a/nixos/doc/manual/configuration/package-mgmt.xml b/nixos/doc/manual/configuration/package-mgmt.xml
deleted file mode 100644
index 2f9395d26fa82..0000000000000
--- a/nixos/doc/manual/configuration/package-mgmt.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="sec-package-management">
- <title>Package Management</title>
- <para>
-  This section describes how to add additional packages to your system. NixOS
-  has two distinct styles of package management:
-  <itemizedlist>
-   <listitem>
-    <para>
-     <emphasis>Declarative</emphasis>, where you declare what packages you want
-     in your <filename>configuration.nix</filename>. Every time you run
-     <command>nixos-rebuild</command>, NixOS will ensure that you get a
-     consistent set of binaries corresponding to your specification.
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     <emphasis>Ad hoc</emphasis>, where you install, upgrade and uninstall
-     packages via the <command>nix-env</command> command. This style allows
-     mixing packages from different Nixpkgs versions. It’s the only choice
-     for non-root users.
-    </para>
-   </listitem>
-  </itemizedlist>
- </para>
- <xi:include href="declarative-packages.xml" />
- <xi:include href="../from_md/configuration/ad-hoc-packages.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/configuration/profiles.chapter.md b/nixos/doc/manual/configuration/profiles.chapter.md
new file mode 100644
index 0000000000000..b4ae1b7d3faaa
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles.chapter.md
@@ -0,0 +1,34 @@
+# Profiles {#ch-profiles}
+
+In some cases, it may be desirable to take advantage of commonly-used,
+predefined configurations provided by nixpkgs, but different from those
+that come as default. This is a role fulfilled by NixOS\'s Profiles,
+which come as files living in `<nixpkgs/nixos/modules/profiles>`. That
+is to say, expected usage is to add them to the imports list of your
+`/etc/configuration.nix` as such:
+
+```nix
+imports = [
+  <nixpkgs/nixos/modules/profiles/profile-name.nix>
+];
+```
+
+Even if some of these profiles seem only useful in the context of
+install media, many are actually intended to be used in real installs.
+
+What follows is a brief explanation on the purpose and use-case for each
+profile. Detailing each option configured by each one is out of scope.
+
+```{=docbook}
+<xi:include href="profiles/all-hardware.section.xml" />
+<xi:include href="profiles/base.section.xml" />
+<xi:include href="profiles/clone-config.section.xml" />
+<xi:include href="profiles/demo.section.xml" />
+<xi:include href="profiles/docker-container.section.xml" />
+<xi:include href="profiles/graphical.section.xml" />
+<xi:include href="profiles/hardened.section.xml" />
+<xi:include href="profiles/headless.section.xml" />
+<xi:include href="profiles/installation-device.section.xml" />
+<xi:include href="profiles/minimal.section.xml" />
+<xi:include href="profiles/qemu-guest.section.xml" />
+```
diff --git a/nixos/doc/manual/configuration/profiles.xml b/nixos/doc/manual/configuration/profiles.xml
deleted file mode 100644
index 6994c7e317056..0000000000000
--- a/nixos/doc/manual/configuration/profiles.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="ch-profiles">
- <title>Profiles</title>
- <para>
-  In some cases, it may be desirable to take advantage of commonly-used,
-  predefined configurations provided by nixpkgs, but different from those that
-  come as default. This is a role fulfilled by NixOS's Profiles, which come as
-  files living in <filename>&lt;nixpkgs/nixos/modules/profiles&gt;</filename>.
-  That is to say, expected usage is to add them to the imports list of your
-  <filename>/etc/configuration.nix</filename> as such:
- </para>
-<programlisting>
-  imports = [
-   &lt;nixpkgs/nixos/modules/profiles/profile-name.nix&gt;
-  ];
-</programlisting>
- <para>
-  Even if some of these profiles seem only useful in the context of install
-  media, many are actually intended to be used in real installs.
- </para>
- <para>
-  What follows is a brief explanation on the purpose and use-case for each
-  profile. Detailing each option configured by each one is out of scope.
- </para>
- <xi:include href="../from_md/configuration/profiles/all-hardware.section.xml" />
- <xi:include href="../from_md/configuration/profiles/base.section.xml" />
- <xi:include href="../from_md/configuration/profiles/clone-config.section.xml" />
- <xi:include href="../from_md/configuration/profiles/demo.section.xml" />
- <xi:include href="../from_md/configuration/profiles/docker-container.section.xml" />
- <xi:include href="../from_md/configuration/profiles/graphical.section.xml" />
- <xi:include href="../from_md/configuration/profiles/hardened.section.xml" />
- <xi:include href="../from_md/configuration/profiles/headless.section.xml" />
- <xi:include href="../from_md/configuration/profiles/installation-device.section.xml" />
- <xi:include href="../from_md/configuration/profiles/minimal.section.xml" />
- <xi:include href="../from_md/configuration/profiles/qemu-guest.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/development/development.xml b/nixos/doc/manual/development/development.xml
index 670a391e38018..0b2ad60a878b0 100644
--- a/nixos/doc/manual/development/development.xml
+++ b/nixos/doc/manual/development/development.xml
@@ -10,10 +10,10 @@
   </para>
  </partintro>
  <xi:include href="../from_md/development/sources.chapter.xml" />
- <xi:include href="writing-modules.xml" />
+ <xi:include href="../from_md/development/writing-modules.chapter.xml" />
  <xi:include href="../from_md/development/building-parts.chapter.xml" />
  <xi:include href="../from_md/development/writing-documentation.chapter.xml" />
  <xi:include href="../from_md/development/building-nixos.chapter.xml" />
- <xi:include href="nixos-tests.xml" />
+ <xi:include href="../from_md/development/nixos-tests.chapter.xml" />
  <xi:include href="../from_md/development/testing-installer.chapter.xml" />
 </part>
diff --git a/nixos/doc/manual/development/nixos-tests.chapter.md b/nixos/doc/manual/development/nixos-tests.chapter.md
new file mode 100644
index 0000000000000..2a4fdddeaa666
--- /dev/null
+++ b/nixos/doc/manual/development/nixos-tests.chapter.md
@@ -0,0 +1,13 @@
+# NixOS Tests {#sec-nixos-tests}
+
+When you add some feature to NixOS, you should write a test for it.
+NixOS tests are kept in the directory `nixos/tests`, and are executed
+(using Nix) by a testing framework that automatically starts one or more
+virtual machines containing the NixOS system(s) required for the test.
+
+```{=docbook}
+<xi:include href="writing-nixos-tests.section.xml" />
+<xi:include href="running-nixos-tests.section.xml" />
+<xi:include href="running-nixos-tests-interactively.section.xml" />
+<xi:include href="linking-nixos-tests-to-packages.section.xml" />
+```
diff --git a/nixos/doc/manual/development/nixos-tests.xml b/nixos/doc/manual/development/nixos-tests.xml
deleted file mode 100644
index 67dc09fc715f2..0000000000000
--- a/nixos/doc/manual/development/nixos-tests.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-        xmlns:xlink="http://www.w3.org/1999/xlink"
-        xmlns:xi="http://www.w3.org/2001/XInclude"
-        version="5.0"
-        xml:id="sec-nixos-tests">
- <title>NixOS Tests</title>
- <para>
-  When you add some feature to NixOS, you should write a test for it. NixOS
-  tests are kept in the directory
-  <filename
-xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/tests">nixos/tests</filename>,
-  and are executed (using Nix) by a testing framework that automatically starts
-  one or more virtual machines containing the NixOS system(s) required for the
-  test.
- </para>
- <xi:include href="../from_md/development/writing-nixos-tests.section.xml" />
- <xi:include href="../from_md/development/running-nixos-tests.section.xml" />
- <xi:include href="../from_md/development/running-nixos-tests-interactively.section.xml" />
- <xi:include href="../from_md/development/linking-nixos-tests-to-packages.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 819c23684cdf7..be56529992ab1 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -38,9 +38,19 @@ The function `mkOption` accepts the following arguments.
     of the module will have to define the value of the option, otherwise
     an error will be thrown.
 
+`defaultText`
+
+:   A textual representation of the default value to be rendered verbatim in
+    the manual. Useful if the default value is a complex expression or depends
+    on other values or packages.
+    Use `lib.literalExpression` for a Nix expression, `lib.literalDocBook` for
+    a plain English description in DocBook format.
+
 `example`
 
 :   An example value that will be shown in the NixOS manual.
+    You can use `lib.literalExpression` and `lib.literalDocBook` in the same way
+    as in `defaultText`.
 
 `description`
 
diff --git a/nixos/doc/manual/development/writing-modules.chapter.md b/nixos/doc/manual/development/writing-modules.chapter.md
new file mode 100644
index 0000000000000..2e3c6b34f1f59
--- /dev/null
+++ b/nixos/doc/manual/development/writing-modules.chapter.md
@@ -0,0 +1,166 @@
+# Writing NixOS Modules {#sec-writing-modules}
+
+NixOS has a modular system for declarative configuration. This system
+combines multiple *modules* to produce the full system configuration.
+One of the modules that constitute the configuration is
+`/etc/nixos/configuration.nix`. Most of the others live in the
+[`nixos/modules`](https://github.com/NixOS/nixpkgs/tree/master/nixos/modules)
+subdirectory of the Nixpkgs tree.
+
+Each NixOS module is a file that handles one logical aspect of the
+configuration, such as a specific kind of hardware, a service, or
+network settings. A module configuration does not have to handle
+everything from scratch; it can use the functionality provided by other
+modules for its implementation. Thus a module can *declare* options that
+can be used by other modules, and conversely can *define* options
+provided by other modules in its own implementation. For example, the
+module
+[`pam.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/pam.nix)
+declares the option `security.pam.services` that allows other modules (e.g.
+[`sshd.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/ssh/sshd.nix))
+to define PAM services; and it defines the option `environment.etc` (declared by
+[`etc.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/etc/etc.nix))
+to cause files to be created in `/etc/pam.d`.
+
+In [](#sec-configuration-syntax), we saw the following structure of
+NixOS modules:
+
+```nix
+{ config, pkgs, ... }:
+
+{ option definitions
+}
+```
+
+This is actually an *abbreviated* form of module that only defines
+options, but does not declare any. The structure of full NixOS modules
+is shown in [Example: Structure of NixOS Modules](#ex-module-syntax).
+
+::: {#ex-module-syntax .example}
+::: {.title}
+**Example: Structure of NixOS Modules**
+:::
+```nix
+{ config, pkgs, ... }:
+
+{
+  imports =
+    [ paths of other modules
+    ];
+
+  options = {
+    option declarations
+  };
+
+  config = {
+    option definitions
+  };
+}
+```
+:::
+
+The meaning of each part is as follows.
+
+-   The first line makes the current Nix expression a function. The variable
+    `pkgs` contains Nixpkgs (by default, it takes the `nixpkgs` entry of
+    `NIX_PATH`, see the [Nix manual](https://nixos.org/manual/nix/stable/#sec-common-env)
+    for further details), while `config` contains the full system
+    configuration. This line can be omitted if there is no reference to
+    `pkgs` and `config` inside the module.
+
+-   This `imports` list enumerates the paths to other NixOS modules that
+    should be included in the evaluation of the system configuration. A
+    default set of modules is defined in the file `modules/module-list.nix`.
+    These don\'t need to be added in the import list.
+
+-   The attribute `options` is a nested set of *option declarations*
+    (described below).
+
+-   The attribute `config` is a nested set of *option definitions* (also
+    described below).
+
+[Example: NixOS Module for the "locate" Service](#locate-example)
+shows a module that handles the regular update of the "locate" database,
+an index of all files in the file system. This module declares two
+options that can be defined by other modules (typically the user's
+`configuration.nix`): `services.locate.enable` (whether the database should
+be updated) and `services.locate.interval` (when the update should be done).
+It implements its functionality by defining two options declared by other
+modules: `systemd.services` (the set of all systemd services) and
+`systemd.timers` (the list of commands to be executed periodically by
+`systemd`).
+
+::: {#locate-example .example}
+::: {.title}
+**Example: NixOS Module for the "locate" Service**
+:::
+```nix
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.locate;
+in {
+  options.services.locate = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled, NixOS will periodically update the database of
+        files used by the locate command.
+      '';
+    };
+
+    interval = mkOption {
+      type = types.str;
+      default = "02:15";
+      example = "hourly";
+      description = ''
+        Update the locate database at this interval. Updates by
+        default at 2:15 AM every day.
+
+        The format is described in
+        systemd.time(7).
+      '';
+    };
+
+    # Other options omitted for documentation
+  };
+
+  config = {
+    systemd.services.update-locatedb =
+      { description = "Update Locate Database";
+        path  = [ pkgs.su ];
+        script =
+          ''
+            mkdir -m 0755 -p $(dirname ${toString cfg.output})
+            exec updatedb \
+              --localuser=${cfg.localuser} \
+              ${optionalString (!cfg.includeStore) "--prunepaths='/nix/store'"} \
+              --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
+          '';
+      };
+
+    systemd.timers.update-locatedb = mkIf cfg.enable
+      { description = "Update timer for locate database";
+        partOf      = [ "update-locatedb.service" ];
+        wantedBy    = [ "timers.target" ];
+        timerConfig.OnCalendar = cfg.interval;
+      };
+  };
+}
+```
+:::
+
+```{=docbook}
+<xi:include href="option-declarations.section.xml" />
+<xi:include href="option-types.section.xml" />
+<xi:include href="option-def.section.xml" />
+<xi:include href="assertions.section.xml" />
+<xi:include href="meta-attributes.section.xml" />
+<xi:include href="importing-modules.section.xml" />
+<xi:include href="replace-modules.section.xml" />
+<xi:include href="freeform-modules.section.xml" />
+<xi:include href="settings-options.section.xml" />
+```
diff --git a/nixos/doc/manual/development/writing-modules.xml b/nixos/doc/manual/development/writing-modules.xml
deleted file mode 100644
index 167976247091b..0000000000000
--- a/nixos/doc/manual/development/writing-modules.xml
+++ /dev/null
@@ -1,191 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-        xmlns:xlink="http://www.w3.org/1999/xlink"
-        xmlns:xi="http://www.w3.org/2001/XInclude"
-        version="5.0"
-        xml:id="sec-writing-modules">
- <title>Writing NixOS Modules</title>
- <para>
-  NixOS has a modular system for declarative configuration. This system
-  combines multiple <emphasis>modules</emphasis> to produce the full system
-  configuration. One of the modules that constitute the configuration is
-  <filename>/etc/nixos/configuration.nix</filename>. Most of the others live in
-  the
-  <link
-xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules"><filename>nixos/modules</filename></link>
-  subdirectory of the Nixpkgs tree.
- </para>
- <para>
-  Each NixOS module is a file that handles one logical aspect of the
-  configuration, such as a specific kind of hardware, a service, or network
-  settings. A module configuration does not have to handle everything from
-  scratch; it can use the functionality provided by other modules for its
-  implementation. Thus a module can <emphasis>declare</emphasis> options that
-  can be used by other modules, and conversely can <emphasis>define</emphasis>
-  options provided by other modules in its own implementation. For example, the
-  module
-  <link
-xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/pam.nix"><filename>pam.nix</filename></link>
-  declares the option <option>security.pam.services</option> that allows other
-  modules (e.g.
-  <link
-xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/ssh/sshd.nix"><filename>sshd.nix</filename></link>)
-  to define PAM services; and it defines the option
-  <option>environment.etc</option> (declared by
-  <link
-xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/etc/etc.nix"><filename>etc.nix</filename></link>)
-  to cause files to be created in <filename>/etc/pam.d</filename>.
- </para>
- <para xml:id="para-module-syn">
-  In <xref
-linkend="sec-configuration-syntax"/>, we saw the following structure
-  of NixOS modules:
-<programlisting>
-{ config, pkgs, ... }:
-
-{ <replaceable>option definitions</replaceable>
-}
-</programlisting>
-  This is actually an <emphasis>abbreviated</emphasis> form of module that only
-  defines options, but does not declare any. The structure of full NixOS
-  modules is shown in <xref linkend='ex-module-syntax' />.
- </para>
- <example xml:id='ex-module-syntax'>
-  <title>Structure of NixOS Modules</title>
-<programlisting>
-{ config, pkgs, ... }: <co xml:id='module-syntax-1' />
-
-{
-  imports =
-    [ <replaceable>paths of other modules</replaceable> <co xml:id='module-syntax-2' />
-    ];
-
-  options = {
-    <replaceable>option declarations</replaceable> <co xml:id='module-syntax-3' />
-  };
-
-  config = {
-    <replaceable>option definitions</replaceable> <co xml:id='module-syntax-4' />
-  };
-}</programlisting>
- </example>
- <para>
-  The meaning of each part is as follows.
-  <calloutlist>
-   <callout arearefs='module-syntax-1'>
-    <para>
-     This line makes the current Nix expression a function. The variable
-     <varname>pkgs</varname> contains Nixpkgs (by default, it takes the
-     <varname>nixpkgs</varname> entry of <envar>NIX_PATH</envar>, see the <link
-     xlink:href="https://nixos.org/manual/nix/stable/#sec-common-env">Nix
-     manual</link> for further details), while <varname>config</varname>
-     contains the full system configuration. This line can be omitted if there
-     is no reference to <varname>pkgs</varname> and <varname>config</varname>
-     inside the module.
-    </para>
-   </callout>
-   <callout arearefs='module-syntax-2'>
-    <para>
-     This list enumerates the paths to other NixOS modules that should be
-     included in the evaluation of the system configuration. A default set of
-     modules is defined in the file
-     <filename>modules/module-list.nix</filename>. These don't need to be added
-     in the import list.
-    </para>
-   </callout>
-   <callout arearefs='module-syntax-3'>
-    <para>
-     The attribute <varname>options</varname> is a nested set of
-     <emphasis>option declarations</emphasis> (described below).
-    </para>
-   </callout>
-   <callout arearefs='module-syntax-4'>
-    <para>
-     The attribute <varname>config</varname> is a nested set of
-     <emphasis>option definitions</emphasis> (also described below).
-    </para>
-   </callout>
-  </calloutlist>
- </para>
- <para>
-  <xref linkend='locate-example' /> shows a module that handles the regular
-  update of the “locate” database, an index of all files in the file
-  system. This module declares two options that can be defined by other modules
-  (typically the user’s <filename>configuration.nix</filename>):
-  <option>services.locate.enable</option> (whether the database should be
-  updated) and <option>services.locate.interval</option> (when the update
-  should be done). It implements its functionality by defining two options
-  declared by other modules: <option>systemd.services</option> (the set of all
-  systemd services) and <option>systemd.timers</option> (the list of commands
-  to be executed periodically by <command>systemd</command>).
- </para>
- <example xml:id='locate-example'>
-  <title>NixOS Module for the “locate” Service</title>
-<programlisting>
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.locate;
-in {
-  options.services.locate = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        If enabled, NixOS will periodically update the database of
-        files used by the <command>locate</command> command.
-      '';
-    };
-
-    interval = mkOption {
-      type = types.str;
-      default = "02:15";
-      example = "hourly";
-      description = ''
-        Update the locate database at this interval. Updates by
-        default at 2:15 AM every day.
-
-        The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
-      '';
-    };
-
-    # Other options omitted for documentation
-  };
-
-  config = {
-    systemd.services.update-locatedb =
-      { description = "Update Locate Database";
-        path  = [ pkgs.su ];
-        script =
-          ''
-            mkdir -m 0755 -p $(dirname ${toString cfg.output})
-            exec updatedb \
-              --localuser=${cfg.localuser} \
-              ${optionalString (!cfg.includeStore) "--prunepaths='/nix/store'"} \
-              --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
-          '';
-      };
-
-    systemd.timers.update-locatedb = mkIf cfg.enable
-      { description = "Update timer for locate database";
-        partOf      = [ "update-locatedb.service" ];
-        wantedBy    = [ "timers.target" ];
-        timerConfig.OnCalendar = cfg.interval;
-      };
-  };
-}
-</programlisting>
- </example>
- <xi:include href="../from_md/development/option-declarations.section.xml" />
- <xi:include href="../from_md/development/option-types.section.xml" />
- <xi:include href="../from_md/development/option-def.section.xml" />
- <xi:include href="../from_md/development/assertions.section.xml" />
- <xi:include href="../from_md/development/meta-attributes.section.xml" />
- <xi:include href="../from_md/development/importing-modules.section.xml" />
- <xi:include href="../from_md/development/replace-modules.section.xml" />
- <xi:include href="../from_md/development/freeform-modules.section.xml" />
- <xi:include href="../from_md/development/settings-options.section.xml" />
-</chapter>
diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index 8471e7608af9f..d9749d37da79f 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -159,6 +159,17 @@ The following methods are available on machine objects:
 `execute`
 
 :   Execute a shell command, returning a list `(status, stdout)`.
+    If the command detaches, it must close stdout, as `execute` will wait
+    for this to consume all output reliably. This can be achieved by
+    redirecting stdout to stderr `>&2`, to `/dev/console`, `/dev/null` or
+    a file. Examples of detaching commands are `sleep 365d &`, where the
+    shell forks a new process that can write to stdout and `xclip -i`, where
+    the `xclip` command itself forks without closing stdout.
+    Takes an optional parameter `check_return` that defaults to `True`.
+    Setting this parameter to `False` will not check for the return code
+    and return -1 instead. This can be used for commands that shut down
+    the VM and would therefore break the pipe that would be used for
+    retrieving the return code.
 
 `succeed`
 
@@ -174,6 +185,9 @@ The following methods are available on machine objects:
 
     -   Dereferencing unset variables fail the command.
 
+    -   It will wait for stdout to be closed. See `execute` for the
+        implications.
+
 `fail`
 
 :   Like `succeed`, but raising an exception if the command returns a zero
diff --git a/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml b/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml
index 0ca98dd6e5101..4243d2bf53f9b 100644
--- a/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml
+++ b/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml
@@ -64,7 +64,8 @@ $ nix-store --optimise
     <para>
       If your <literal>/boot</literal> partition runs out of space,
       after clearing old profiles you must rebuild your system with
-      <literal>nixos-rebuild</literal> to update the
+      <literal>nixos-rebuild boot</literal> or
+      <literal>nixos-rebuild switch</literal> to update the
       <literal>/boot</literal> partition and clear space.
     </para>
   </section>
diff --git a/nixos/doc/manual/from_md/administration/containers.chapter.xml b/nixos/doc/manual/from_md/administration/containers.chapter.xml
new file mode 100644
index 0000000000000..afbd5b35aaa5c
--- /dev/null
+++ b/nixos/doc/manual/from_md/administration/containers.chapter.xml
@@ -0,0 +1,31 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-containers">
+  <title>Container Management</title>
+  <para>
+    NixOS allows you to easily run other NixOS instances as
+    <emphasis>containers</emphasis>. Containers are a light-weight
+    approach to virtualisation that runs software in the container at
+    the same speed as in the host system. NixOS containers share the Nix
+    store of the host, making container creation very efficient.
+  </para>
+  <warning>
+    <para>
+      Currently, NixOS containers are not perfectly isolated from the
+      host system. This means that a user with root access to the
+      container can do things that affect the host. So you should not
+      give container root access to untrusted users.
+    </para>
+  </warning>
+  <para>
+    NixOS containers can be created in two ways: imperatively, using the
+    command <literal>nixos-container</literal>, and declaratively, by
+    specifying them in your <literal>configuration.nix</literal>. The
+    declarative approach implies that containers get upgraded along with
+    your host system when you run <literal>nixos-rebuild</literal>,
+    which is often not what you want. By contrast, in the imperative
+    approach, containers are configured and updated independently from
+    the host system.
+  </para>
+  <xi:include href="imperative-containers.section.xml" />
+  <xi:include href="declarative-containers.section.xml" />
+  <xi:include href="container-networking.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml b/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml
new file mode 100644
index 0000000000000..8bbb8a1fe7292
--- /dev/null
+++ b/nixos/doc/manual/from_md/administration/troubleshooting.chapter.xml
@@ -0,0 +1,12 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-troubleshooting">
+  <title>Troubleshooting</title>
+  <para>
+    This chapter describes solutions to common problems you might
+    encounter when you manage your NixOS system.
+  </para>
+  <xi:include href="boot-problems.section.xml" />
+  <xi:include href="maintenance-mode.section.xml" />
+  <xi:include href="rollback.section.xml" />
+  <xi:include href="store-corruption.section.xml" />
+  <xi:include href="network-problems.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml b/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
new file mode 100644
index 0000000000000..01446e53e38ff
--- /dev/null
+++ b/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
@@ -0,0 +1,21 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-configuration-syntax">
+  <title>Configuration Syntax</title>
+  <para>
+    The NixOS configuration file
+    <literal>/etc/nixos/configuration.nix</literal> is actually a
+    <emphasis>Nix expression</emphasis>, which is the Nix package
+    manager’s purely functional language for describing how to build
+    packages and configurations. This means you have all the expressive
+    power of that language at your disposal, including the ability to
+    abstract over common patterns, which is very useful when managing
+    complex systems. The syntax and semantics of the Nix language are
+    fully described in the
+    <link xlink:href="https://nixos.org/nix/manual/#chap-writing-nix-expressions">Nix
+    manual</link>, but here we give a short overview of the most
+    important constructs useful in NixOS configuration files.
+  </para>
+  <xi:include href="config-file.section.xml" />
+  <xi:include href="abstractions.section.xml" />
+  <xi:include href="modularity.section.xml" />
+  <xi:include href="summary.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml b/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml
new file mode 100644
index 0000000000000..da31f18d9233e
--- /dev/null
+++ b/nixos/doc/manual/from_md/configuration/declarative-packages.section.xml
@@ -0,0 +1,53 @@
+<section xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-declarative-package-mgmt">
+  <title>Declarative Package Management</title>
+  <para>
+    With declarative package management, you specify which packages you
+    want on your system by setting the option
+    <xref linkend="opt-environment.systemPackages" />. For instance,
+    adding the following line to <literal>configuration.nix</literal>
+    enables the Mozilla Thunderbird email application:
+  </para>
+  <programlisting language="bash">
+environment.systemPackages = [ pkgs.thunderbird ];
+</programlisting>
+  <para>
+    The effect of this specification is that the Thunderbird package
+    from Nixpkgs will be built or downloaded as part of the system when
+    you run <literal>nixos-rebuild switch</literal>.
+  </para>
+  <note>
+    <para>
+      Some packages require additional global configuration such as
+      D-Bus or systemd service registration so adding them to
+      <xref linkend="opt-environment.systemPackages" /> might not be
+      sufficient. You are advised to check the
+      <link linkend="ch-options">list of options</link> whether a NixOS
+      module for the package does not exist.
+    </para>
+  </note>
+  <para>
+    You can get a list of the available packages as follows:
+  </para>
+  <programlisting>
+$ nix-env -qaP '*' --description
+nixos.firefox   firefox-23.0   Mozilla Firefox - the browser, reloaded
+...
+</programlisting>
+  <para>
+    The first column in the output is the <emphasis>attribute
+    name</emphasis>, such as <literal>nixos.thunderbird</literal>.
+  </para>
+  <para>
+    Note: the <literal>nixos</literal> prefix tells us that we want to
+    get the package from the <literal>nixos</literal> channel and works
+    only in CLI tools. In declarative configuration use
+    <literal>pkgs</literal> prefix (variable).
+  </para>
+  <para>
+    To <quote>uninstall</quote> a package, simply remove it from
+    <xref linkend="opt-environment.systemPackages" /> and run
+    <literal>nixos-rebuild switch</literal>.
+  </para>
+  <xi:include href="customizing-packages.section.xml" />
+  <xi:include href="adding-custom-packages.section.xml" />
+</section>
diff --git a/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml b/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml
new file mode 100644
index 0000000000000..71441d8b4a5b3
--- /dev/null
+++ b/nixos/doc/manual/from_md/configuration/file-systems.chapter.xml
@@ -0,0 +1,55 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-file-systems">
+  <title>File Systems</title>
+  <para>
+    You can define file systems using the <literal>fileSystems</literal>
+    configuration option. For instance, the following definition causes
+    NixOS to mount the Ext4 file system on device
+    <literal>/dev/disk/by-label/data</literal> onto the mount point
+    <literal>/data</literal>:
+  </para>
+  <programlisting language="bash">
+fileSystems.&quot;/data&quot; =
+  { device = &quot;/dev/disk/by-label/data&quot;;
+    fsType = &quot;ext4&quot;;
+  };
+</programlisting>
+  <para>
+    This will create an entry in <literal>/etc/fstab</literal>, which
+    will generate a corresponding
+    <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.mount.html">systemd.mount</link>
+    unit via
+    <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html">systemd-fstab-generator</link>.
+    The filesystem will be mounted automatically unless
+    <literal>&quot;noauto&quot;</literal> is present in
+    <link linkend="opt-fileSystems._name_.options">options</link>.
+    <literal>&quot;noauto&quot;</literal> filesystems can be mounted
+    explicitly using <literal>systemctl</literal> e.g.
+    <literal>systemctl start data.mount</literal>. Mount points are
+    created automatically if they don’t already exist. For
+    <literal>device</literal>, it’s best to use the topology-independent
+    device aliases in <literal>/dev/disk/by-label</literal> and
+    <literal>/dev/disk/by-uuid</literal>, as these don’t change if the
+    topology changes (e.g. if a disk is moved to another IDE
+    controller).
+  </para>
+  <para>
+    You can usually omit the file system type
+    (<literal>fsType</literal>), since <literal>mount</literal> can
+    usually detect the type and load the necessary kernel module
+    automatically. However, if the file system is needed at early boot
+    (in the initial ramdisk) and is not <literal>ext2</literal>,
+    <literal>ext3</literal> or <literal>ext4</literal>, then it’s best
+    to specify <literal>fsType</literal> to ensure that the kernel
+    module is available.
+  </para>
+  <note>
+    <para>
+      System startup will fail if any of the filesystems fails to mount,
+      dropping you to the emergency shell. You can make a mount
+      asynchronous and non-critical by adding
+      <literal>options = [ &quot;nofail&quot; ];</literal>.
+    </para>
+  </note>
+  <xi:include href="luks-file-systems.section.xml" />
+  <xi:include href="sshfs-file-systems.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml b/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
index f804d0a3b8c2a..a1d6815af29c1 100644
--- a/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
@@ -6,7 +6,7 @@
     selects the Linux 3.10 kernel:
   </para>
   <programlisting language="bash">
-boot.kernelPackages = pkgs.linuxPackages_3_10;
+boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_10;
 </programlisting>
   <para>
     Note that this not only replaces the kernel, but also packages that
@@ -15,6 +15,13 @@ boot.kernelPackages = pkgs.linuxPackages_3_10;
     kernel.
   </para>
   <para>
+    While <literal>pkgs.linuxKernel.packages</literal> contains all
+    available kernel packages, you may want to use one of the
+    unversioned <literal>pkgs.linuxPackages_*</literal> aliases such as
+    <literal>pkgs.linuxPackages_latest</literal>, that are kept up to
+    date with new versions.
+  </para>
+  <para>
     The default Linux kernel configuration should be fine for most
     users. You can see the configuration of your current kernel with the
     following command:
@@ -29,14 +36,13 @@ zcat /proc/config.gz
     enable support for the kernel debugger KGDB:
   </para>
   <programlisting language="bash">
-nixpkgs.config.packageOverrides = pkgs:
-  { linux_3_4 = pkgs.linux_3_4.override {
-      extraConfig =
-        ''
-          KGDB y
-        '';
-    };
+nixpkgs.config.packageOverrides = pkgs: pkgs.lib.recursiveUpdate pkgs {
+  linuxKernel.kernels.linux_5_10 = pkgs.linuxKernel.kernels.linux_5_10.override {
+    extraConfig = ''
+      KGDB y
+    '';
   };
+};
 </programlisting>
   <para>
     <literal>extraConfig</literal> takes a list of Linux kernel
@@ -82,16 +88,17 @@ boot.kernel.sysctl.&quot;net.ipv4.tcp_keepalive_time&quot; = 120;
       The first step before compiling the kernel is to generate an
       appropriate <literal>.config</literal> configuration. Either you
       pass your own config via the <literal>configfile</literal> setting
-      of <literal>linuxManualConfig</literal>:
+      of <literal>linuxKernel.manualConfig</literal>:
     </para>
     <programlisting language="bash">
-custom-kernel = super.linuxManualConfig {
-  inherit (super) stdenv hostPlatform;
-  inherit (linux_4_9) src;
-  version = &quot;${linux_4_9.version}-custom&quot;;
+custom-kernel = let base_kernel = linuxKernel.kernels.linux_4_9;
+  in super.linuxKernel.manualConfig {
+    inherit (super) stdenv hostPlatform;
+    inherit (base_kernel) src;
+    version = &quot;${base_kernel.version}-custom&quot;;
 
-  configfile = /home/me/my_kernel_config;
-  allowImportFromDerivation = true;
+    configfile = /home/me/my_kernel_config;
+    allowImportFromDerivation = true;
 };
 </programlisting>
     <para>
diff --git a/nixos/doc/manual/from_md/configuration/networking.chapter.xml b/nixos/doc/manual/from_md/configuration/networking.chapter.xml
new file mode 100644
index 0000000000000..2ed86ea3b5899
--- /dev/null
+++ b/nixos/doc/manual/from_md/configuration/networking.chapter.xml
@@ -0,0 +1,15 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-networking">
+  <title>Networking</title>
+  <para>
+    This section describes how to configure networking components on
+    your NixOS machine.
+  </para>
+  <xi:include href="network-manager.section.xml" />
+  <xi:include href="ssh.section.xml" />
+  <xi:include href="ipv4-config.section.xml" />
+  <xi:include href="ipv6-config.section.xml" />
+  <xi:include href="firewall.section.xml" />
+  <xi:include href="wireless.section.xml" />
+  <xi:include href="ad-hoc-network-config.section.xml" />
+  <xi:include href="renaming-interfaces.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml b/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml
new file mode 100644
index 0000000000000..d3727edbe08d3
--- /dev/null
+++ b/nixos/doc/manual/from_md/configuration/package-mgmt.chapter.xml
@@ -0,0 +1,28 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-package-management">
+  <title>Package Management</title>
+  <para>
+    This section describes how to add additional packages to your
+    system. NixOS has two distinct styles of package management:
+  </para>
+  <itemizedlist>
+    <listitem>
+      <para>
+        <emphasis>Declarative</emphasis>, where you declare what
+        packages you want in your <literal>configuration.nix</literal>.
+        Every time you run <literal>nixos-rebuild</literal>, NixOS will
+        ensure that you get a consistent set of binaries corresponding
+        to your specification.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        <emphasis>Ad hoc</emphasis>, where you install, upgrade and
+        uninstall packages via the <literal>nix-env</literal> command.
+        This style allows mixing packages from different Nixpkgs
+        versions. It’s the only choice for non-root users.
+      </para>
+    </listitem>
+  </itemizedlist>
+  <xi:include href="declarative-packages.section.xml" />
+  <xi:include href="ad-hoc-packages.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/configuration/profiles.chapter.xml b/nixos/doc/manual/from_md/configuration/profiles.chapter.xml
new file mode 100644
index 0000000000000..6f5fc130c6a07
--- /dev/null
+++ b/nixos/doc/manual/from_md/configuration/profiles.chapter.xml
@@ -0,0 +1,38 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="ch-profiles">
+  <title>Profiles</title>
+  <para>
+    In some cases, it may be desirable to take advantage of
+    commonly-used, predefined configurations provided by nixpkgs, but
+    different from those that come as default. This is a role fulfilled
+    by NixOS's Profiles, which come as files living in
+    <literal>&lt;nixpkgs/nixos/modules/profiles&gt;</literal>. That is
+    to say, expected usage is to add them to the imports list of your
+    <literal>/etc/configuration.nix</literal> as such:
+  </para>
+  <programlisting language="bash">
+imports = [
+  &lt;nixpkgs/nixos/modules/profiles/profile-name.nix&gt;
+];
+</programlisting>
+  <para>
+    Even if some of these profiles seem only useful in the context of
+    install media, many are actually intended to be used in real
+    installs.
+  </para>
+  <para>
+    What follows is a brief explanation on the purpose and use-case for
+    each profile. Detailing each option configured by each one is out of
+    scope.
+  </para>
+  <xi:include href="profiles/all-hardware.section.xml" />
+  <xi:include href="profiles/base.section.xml" />
+  <xi:include href="profiles/clone-config.section.xml" />
+  <xi:include href="profiles/demo.section.xml" />
+  <xi:include href="profiles/docker-container.section.xml" />
+  <xi:include href="profiles/graphical.section.xml" />
+  <xi:include href="profiles/hardened.section.xml" />
+  <xi:include href="profiles/headless.section.xml" />
+  <xi:include href="profiles/installation-device.section.xml" />
+  <xi:include href="profiles/minimal.section.xml" />
+  <xi:include href="profiles/qemu-guest.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml b/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml
new file mode 100644
index 0000000000000..b9ff2269676cf
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/nixos-tests.chapter.xml
@@ -0,0 +1,14 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-nixos-tests">
+  <title>NixOS Tests</title>
+  <para>
+    When you add some feature to NixOS, you should write a test for it.
+    NixOS tests are kept in the directory
+    <literal>nixos/tests</literal>, and are executed (using Nix) by a
+    testing framework that automatically starts one or more virtual
+    machines containing the NixOS system(s) required for the test.
+  </para>
+  <xi:include href="writing-nixos-tests.section.xml" />
+  <xi:include href="running-nixos-tests.section.xml" />
+  <xi:include href="running-nixos-tests-interactively.section.xml" />
+  <xi:include href="linking-nixos-tests-to-packages.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixos/doc/manual/from_md/development/option-declarations.section.xml
index 85a59a543d14e..2845e37659b1b 100644
--- a/nixos/doc/manual/from_md/development/option-declarations.section.xml
+++ b/nixos/doc/manual/from_md/development/option-declarations.section.xml
@@ -59,11 +59,29 @@ options = {
     </varlistentry>
     <varlistentry>
       <term>
+        <literal>defaultText</literal>
+      </term>
+      <listitem>
+        <para>
+          A textual representation of the default value to be rendered
+          verbatim in the manual. Useful if the default value is a
+          complex expression or depends on other values or packages. Use
+          <literal>lib.literalExpression</literal> for a Nix expression,
+          <literal>lib.literalDocBook</literal> for a plain English
+          description in DocBook format.
+        </para>
+      </listitem>
+    </varlistentry>
+    <varlistentry>
+      <term>
         <literal>example</literal>
       </term>
       <listitem>
         <para>
-          An example value that will be shown in the NixOS manual.
+          An example value that will be shown in the NixOS manual. You
+          can use <literal>lib.literalExpression</literal> and
+          <literal>lib.literalDocBook</literal> in the same way as in
+          <literal>defaultText</literal>.
         </para>
       </listitem>
     </varlistentry>
diff --git a/nixos/doc/manual/from_md/development/writing-modules.chapter.xml b/nixos/doc/manual/from_md/development/writing-modules.chapter.xml
new file mode 100644
index 0000000000000..e33c24f4f12c2
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/writing-modules.chapter.xml
@@ -0,0 +1,196 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-writing-modules">
+  <title>Writing NixOS Modules</title>
+  <para>
+    NixOS has a modular system for declarative configuration. This
+    system combines multiple <emphasis>modules</emphasis> to produce the
+    full system configuration. One of the modules that constitute the
+    configuration is <literal>/etc/nixos/configuration.nix</literal>.
+    Most of the others live in the
+    <link xlink:href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules"><literal>nixos/modules</literal></link>
+    subdirectory of the Nixpkgs tree.
+  </para>
+  <para>
+    Each NixOS module is a file that handles one logical aspect of the
+    configuration, such as a specific kind of hardware, a service, or
+    network settings. A module configuration does not have to handle
+    everything from scratch; it can use the functionality provided by
+    other modules for its implementation. Thus a module can
+    <emphasis>declare</emphasis> options that can be used by other
+    modules, and conversely can <emphasis>define</emphasis> options
+    provided by other modules in its own implementation. For example,
+    the module
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/pam.nix"><literal>pam.nix</literal></link>
+    declares the option <literal>security.pam.services</literal> that
+    allows other modules (e.g.
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/ssh/sshd.nix"><literal>sshd.nix</literal></link>)
+    to define PAM services; and it defines the option
+    <literal>environment.etc</literal> (declared by
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/etc/etc.nix"><literal>etc.nix</literal></link>)
+    to cause files to be created in <literal>/etc/pam.d</literal>.
+  </para>
+  <para>
+    In <xref linkend="sec-configuration-syntax" />, we saw the following
+    structure of NixOS modules:
+  </para>
+  <programlisting language="bash">
+{ config, pkgs, ... }:
+
+{ option definitions
+}
+</programlisting>
+  <para>
+    This is actually an <emphasis>abbreviated</emphasis> form of module
+    that only defines options, but does not declare any. The structure
+    of full NixOS modules is shown in
+    <link linkend="ex-module-syntax">Example: Structure of NixOS
+    Modules</link>.
+  </para>
+  <anchor xml:id="ex-module-syntax" />
+  <para>
+    <emphasis role="strong">Example: Structure of NixOS
+    Modules</emphasis>
+  </para>
+  <programlisting language="bash">
+{ config, pkgs, ... }:
+
+{
+  imports =
+    [ paths of other modules
+    ];
+
+  options = {
+    option declarations
+  };
+
+  config = {
+    option definitions
+  };
+}
+</programlisting>
+  <para>
+    The meaning of each part is as follows.
+  </para>
+  <itemizedlist>
+    <listitem>
+      <para>
+        The first line makes the current Nix expression a function. The
+        variable <literal>pkgs</literal> contains Nixpkgs (by default,
+        it takes the <literal>nixpkgs</literal> entry of
+        <literal>NIX_PATH</literal>, see the
+        <link xlink:href="https://nixos.org/manual/nix/stable/#sec-common-env">Nix
+        manual</link> for further details), while
+        <literal>config</literal> contains the full system
+        configuration. This line can be omitted if there is no reference
+        to <literal>pkgs</literal> and <literal>config</literal> inside
+        the module.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        This <literal>imports</literal> list enumerates the paths to
+        other NixOS modules that should be included in the evaluation of
+        the system configuration. A default set of modules is defined in
+        the file <literal>modules/module-list.nix</literal>. These don't
+        need to be added in the import list.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        The attribute <literal>options</literal> is a nested set of
+        <emphasis>option declarations</emphasis> (described below).
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        The attribute <literal>config</literal> is a nested set of
+        <emphasis>option definitions</emphasis> (also described below).
+      </para>
+    </listitem>
+  </itemizedlist>
+  <para>
+    <link linkend="locate-example">Example: NixOS Module for the
+    <quote>locate</quote> Service</link> shows a module that handles the
+    regular update of the <quote>locate</quote> database, an index of
+    all files in the file system. This module declares two options that
+    can be defined by other modules (typically the user’s
+    <literal>configuration.nix</literal>):
+    <literal>services.locate.enable</literal> (whether the database
+    should be updated) and <literal>services.locate.interval</literal>
+    (when the update should be done). It implements its functionality by
+    defining two options declared by other modules:
+    <literal>systemd.services</literal> (the set of all systemd
+    services) and <literal>systemd.timers</literal> (the list of
+    commands to be executed periodically by <literal>systemd</literal>).
+  </para>
+  <anchor xml:id="locate-example" />
+  <para>
+    <emphasis role="strong">Example: NixOS Module for the
+    <quote>locate</quote> Service</emphasis>
+  </para>
+  <programlisting language="bash">
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.locate;
+in {
+  options.services.locate = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled, NixOS will periodically update the database of
+        files used by the locate command.
+      '';
+    };
+
+    interval = mkOption {
+      type = types.str;
+      default = &quot;02:15&quot;;
+      example = &quot;hourly&quot;;
+      description = ''
+        Update the locate database at this interval. Updates by
+        default at 2:15 AM every day.
+
+        The format is described in
+        systemd.time(7).
+      '';
+    };
+
+    # Other options omitted for documentation
+  };
+
+  config = {
+    systemd.services.update-locatedb =
+      { description = &quot;Update Locate Database&quot;;
+        path  = [ pkgs.su ];
+        script =
+          ''
+            mkdir -m 0755 -p $(dirname ${toString cfg.output})
+            exec updatedb \
+              --localuser=${cfg.localuser} \
+              ${optionalString (!cfg.includeStore) &quot;--prunepaths='/nix/store'&quot;} \
+              --output=${toString cfg.output} ${concatStringsSep &quot; &quot; cfg.extraFlags}
+          '';
+      };
+
+    systemd.timers.update-locatedb = mkIf cfg.enable
+      { description = &quot;Update timer for locate database&quot;;
+        partOf      = [ &quot;update-locatedb.service&quot; ];
+        wantedBy    = [ &quot;timers.target&quot; ];
+        timerConfig.OnCalendar = cfg.interval;
+      };
+  };
+}
+</programlisting>
+  <xi:include href="option-declarations.section.xml" />
+  <xi:include href="option-types.section.xml" />
+  <xi:include href="option-def.section.xml" />
+  <xi:include href="assertions.section.xml" />
+  <xi:include href="meta-attributes.section.xml" />
+  <xi:include href="importing-modules.section.xml" />
+  <xi:include href="replace-modules.section.xml" />
+  <xi:include href="freeform-modules.section.xml" />
+  <xi:include href="settings-options.section.xml" />
+</chapter>
diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
index 83a96d5bb224e..0d523681b6393 100644
--- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
@@ -266,7 +266,23 @@ start_all()
       <listitem>
         <para>
           Execute a shell command, returning a list
-          <literal>(status, stdout)</literal>.
+          <literal>(status, stdout)</literal>. If the command detaches,
+          it must close stdout, as <literal>execute</literal> will wait
+          for this to consume all output reliably. This can be achieved
+          by redirecting stdout to stderr <literal>&gt;&amp;2</literal>,
+          to <literal>/dev/console</literal>,
+          <literal>/dev/null</literal> or a file. Examples of detaching
+          commands are <literal>sleep 365d &amp;</literal>, where the
+          shell forks a new process that can write to stdout and
+          <literal>xclip -i</literal>, where the
+          <literal>xclip</literal> command itself forks without closing
+          stdout. Takes an optional parameter
+          <literal>check_return</literal> that defaults to
+          <literal>True</literal>. Setting this parameter to
+          <literal>False</literal> will not check for the return code
+          and return -1 instead. This can be used for commands that shut
+          down the VM and would therefore break the pipe that would be
+          used for retrieving the return code.
         </para>
       </listitem>
     </varlistentry>
@@ -300,6 +316,12 @@ start_all()
               Dereferencing unset variables fail the command.
             </para>
           </listitem>
+          <listitem>
+            <para>
+              It will wait for stdout to be closed. See
+              <literal>execute</literal> for the implications.
+            </para>
+          </listitem>
         </itemizedlist>
       </listitem>
     </varlistentry>
diff --git a/nixos/doc/manual/from_md/installation/installing.chapter.xml b/nixos/doc/manual/from_md/installation/installing.chapter.xml
new file mode 100644
index 0000000000000..db073fa839653
--- /dev/null
+++ b/nixos/doc/manual/from_md/installation/installing.chapter.xml
@@ -0,0 +1,645 @@
+<chapter xmlns="http://docbook.org/ns/docbook"  xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="sec-installation">
+  <title>Installing NixOS</title>
+  <section xml:id="sec-installation-booting">
+    <title>Booting the system</title>
+    <para>
+      NixOS can be installed on BIOS or UEFI systems. The procedure for
+      a UEFI installation is by and large the same as a BIOS
+      installation. The differences are mentioned in the steps that
+      follow.
+    </para>
+    <para>
+      The installation media can be burned to a CD, or now more
+      commonly, <quote>burned</quote> to a USB drive (see
+      <xref linkend="sec-booting-from-usb" />).
+    </para>
+    <para>
+      The installation media contains a basic NixOS installation. When
+      it’s finished booting, it should have detected most of your
+      hardware.
+    </para>
+    <para>
+      The NixOS manual is available by running
+      <literal>nixos-help</literal>.
+    </para>
+    <para>
+      You are logged-in automatically as <literal>nixos</literal>. The
+      <literal>nixos</literal> user account has an empty password so you
+      can use <literal>sudo</literal> without a password:
+    </para>
+    <programlisting>
+$ sudo -i
+</programlisting>
+    <para>
+      If you downloaded the graphical ISO image, you can run
+      <literal>systemctl start display-manager</literal> to start the
+      desktop environment. If you want to continue on the terminal, you
+      can use <literal>loadkeys</literal> to switch to your preferred
+      keyboard layout. (We even provide neo2 via
+      <literal>loadkeys de neo</literal>!)
+    </para>
+    <para>
+      If the text is too small to be legible, try
+      <literal>setfont ter-v32n</literal> to increase the font size.
+    </para>
+    <para>
+      To install over a serial port connect with
+      <literal>115200n8</literal> (e.g.
+      <literal>picocom -b 115200 /dev/ttyUSB0</literal>). When the
+      bootloader lists boot entries, select the serial console boot
+      entry.
+    </para>
+    <section xml:id="sec-installation-booting-networking">
+      <title>Networking in the installer</title>
+      <para>
+        The boot process should have brought up networking (check
+        <literal>ip a</literal>). Networking is necessary for the
+        installer, since it will download lots of stuff (such as source
+        tarballs or Nixpkgs channel binaries). It’s best if you have a
+        DHCP server on your network. Otherwise configure networking
+        manually using <literal>ifconfig</literal>.
+      </para>
+      <para>
+        On the graphical installer, you can configure the network, wifi
+        included, through NetworkManager. Using the
+        <literal>nmtui</literal> program, you can do so even in a
+        non-graphical session. If you prefer to configure the network
+        manually, disable NetworkManager with
+        <literal>systemctl stop NetworkManager</literal>.
+      </para>
+      <para>
+        On the minimal installer, NetworkManager is not available, so
+        configuration must be perfomed manually. To configure the wifi,
+        first start wpa_supplicant with
+        <literal>sudo systemctl start wpa_supplicant</literal>, then run
+        <literal>wpa_cli</literal>. For most home networks, you need to
+        type in the following commands:
+      </para>
+      <programlisting>
+&gt; add_network
+0
+&gt; set_network 0 ssid &quot;myhomenetwork&quot;
+OK
+&gt; set_network 0 psk &quot;mypassword&quot;
+OK
+&gt; set_network 0 key_mgmt WPA-PSK
+OK
+&gt; enable_network 0
+OK
+</programlisting>
+      <para>
+        For enterprise networks, for example
+        <emphasis>eduroam</emphasis>, instead do:
+      </para>
+      <programlisting>
+&gt; add_network
+0
+&gt; set_network 0 ssid &quot;eduroam&quot;
+OK
+&gt; set_network 0 identity &quot;myname@example.com&quot;
+OK
+&gt; set_network 0 password &quot;mypassword&quot;
+OK
+&gt; set_network 0 key_mgmt WPA-EAP
+OK
+&gt; enable_network 0
+OK
+</programlisting>
+      <para>
+        When successfully connected, you should see a line such as this
+        one
+      </para>
+      <programlisting>
+&lt;3&gt;CTRL-EVENT-CONNECTED - Connection to 32:85:ab:ef:24:5c completed [id=0 id_str=]
+</programlisting>
+      <para>
+        you can now leave <literal>wpa_cli</literal> by typing
+        <literal>quit</literal>.
+      </para>
+      <para>
+        If you would like to continue the installation from a different
+        machine you can use activated SSH daemon. You need to copy your
+        ssh key to either
+        <literal>/home/nixos/.ssh/authorized_keys</literal> or
+        <literal>/root/.ssh/authorized_keys</literal> (Tip: For
+        installers with a modifiable filesystem such as the sd-card
+        installer image a key can be manually placed by mounting the
+        image on a different machine). Alternatively you must set a
+        password for either <literal>root</literal> or
+        <literal>nixos</literal> with <literal>passwd</literal> to be
+        able to login.
+      </para>
+    </section>
+  </section>
+  <section xml:id="sec-installation-partitioning">
+    <title>Partitioning and formatting</title>
+    <para>
+      The NixOS installer doesn’t do any partitioning or formatting, so
+      you need to do that yourself.
+    </para>
+    <para>
+      The NixOS installer ships with multiple partitioning tools. The
+      examples below use <literal>parted</literal>, but also provides
+      <literal>fdisk</literal>, <literal>gdisk</literal>,
+      <literal>cfdisk</literal>, and <literal>cgdisk</literal>.
+    </para>
+    <para>
+      The recommended partition scheme differs depending if the computer
+      uses <emphasis>Legacy Boot</emphasis> or
+      <emphasis>UEFI</emphasis>.
+    </para>
+    <section xml:id="sec-installation-partitioning-UEFI">
+      <title>UEFI (GPT)</title>
+      <para>
+        Here's an example partition scheme for UEFI, using
+        <literal>/dev/sda</literal> as the device.
+      </para>
+      <note>
+        <para>
+          You can safely ignore <literal>parted</literal>'s
+          informational message about needing to update /etc/fstab.
+        </para>
+      </note>
+      <orderedlist numeration="arabic">
+        <listitem>
+          <para>
+            Create a <emphasis>GPT</emphasis> partition table.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mklabel gpt
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            Add the <emphasis>root</emphasis> partition. This will fill
+            the disk except for the end part, where the swap will live,
+            and the space left in front (512MiB) which will be used by
+            the boot partition.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mkpart primary 512MiB -8GiB
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            Next, add a <emphasis>swap</emphasis> partition. The size
+            required will vary according to needs, here a 8GiB one is
+            created.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+</programlisting>
+          <note>
+            <para>
+              The swap partition size rules are no different than for
+              other Linux distributions.
+            </para>
+          </note>
+        </listitem>
+        <listitem>
+          <para>
+            Finally, the <emphasis>boot</emphasis> partition. NixOS by
+            default uses the ESP (EFI system partition) as its
+            <emphasis>/boot</emphasis> partition. It uses the initially
+            reserved 512MiB at the start of the disk.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+# parted /dev/sda -- set 3 esp on
+</programlisting>
+        </listitem>
+      </orderedlist>
+      <para>
+        Once complete, you can follow with
+        <xref linkend="sec-installation-partitioning-formatting" />.
+      </para>
+    </section>
+    <section xml:id="sec-installation-partitioning-MBR">
+      <title>Legacy Boot (MBR)</title>
+      <para>
+        Here's an example partition scheme for Legacy Boot, using
+        <literal>/dev/sda</literal> as the device.
+      </para>
+      <note>
+        <para>
+          You can safely ignore <literal>parted</literal>'s
+          informational message about needing to update /etc/fstab.
+        </para>
+      </note>
+      <orderedlist numeration="arabic">
+        <listitem>
+          <para>
+            Create a <emphasis>MBR</emphasis> partition table.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mklabel msdos
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            Add the <emphasis>root</emphasis> partition. This will fill
+            the the disk except for the end part, where the swap will
+            live.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mkpart primary 1MiB -8GiB
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            Finally, add a <emphasis>swap</emphasis> partition. The size
+            required will vary according to needs, here a 8GiB one is
+            created.
+          </para>
+          <programlisting>
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+</programlisting>
+          <note>
+            <para>
+              The swap partition size rules are no different than for
+              other Linux distributions.
+            </para>
+          </note>
+        </listitem>
+      </orderedlist>
+      <para>
+        Once complete, you can follow with
+        <xref linkend="sec-installation-partitioning-formatting" />.
+      </para>
+    </section>
+    <section xml:id="sec-installation-partitioning-formatting">
+      <title>Formatting</title>
+      <para>
+        Use the following commands:
+      </para>
+      <itemizedlist>
+        <listitem>
+          <para>
+            For initialising Ext4 partitions:
+            <literal>mkfs.ext4</literal>. It is recommended that you
+            assign a unique symbolic label to the file system using the
+            option <literal>-L label</literal>, since this makes the
+            file system configuration independent from device changes.
+            For example:
+          </para>
+          <programlisting>
+# mkfs.ext4 -L nixos /dev/sda1
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            For creating swap partitions: <literal>mkswap</literal>.
+            Again it’s recommended to assign a label to the swap
+            partition: <literal>-L label</literal>. For example:
+          </para>
+          <programlisting>
+# mkswap -L swap /dev/sda2
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            <emphasis role="strong">UEFI systems</emphasis>
+          </para>
+          <para>
+            For creating boot partitions: <literal>mkfs.fat</literal>.
+            Again it’s recommended to assign a label to the boot
+            partition: <literal>-n label</literal>. For example:
+          </para>
+          <programlisting>
+# mkfs.fat -F 32 -n boot /dev/sda3
+</programlisting>
+        </listitem>
+        <listitem>
+          <para>
+            For creating LVM volumes, the LVM commands, e.g.,
+            <literal>pvcreate</literal>, <literal>vgcreate</literal>,
+            and <literal>lvcreate</literal>.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            For creating software RAID devices, use
+            <literal>mdadm</literal>.
+          </para>
+        </listitem>
+      </itemizedlist>
+    </section>
+  </section>
+  <section xml:id="sec-installation-installing">
+    <title>Installing</title>
+    <orderedlist numeration="arabic">
+      <listitem>
+        <para>
+          Mount the target file system on which NixOS should be
+          installed on <literal>/mnt</literal>, e.g.
+        </para>
+        <programlisting>
+# mount /dev/disk/by-label/nixos /mnt
+</programlisting>
+      </listitem>
+      <listitem>
+        <para>
+          <emphasis role="strong">UEFI systems</emphasis>
+        </para>
+        <para>
+          Mount the boot file system on <literal>/mnt/boot</literal>,
+          e.g.
+        </para>
+        <programlisting>
+# mkdir -p /mnt/boot
+# mount /dev/disk/by-label/boot /mnt/boot
+</programlisting>
+      </listitem>
+      <listitem>
+        <para>
+          If your machine has a limited amount of memory, you may want
+          to activate swap devices now
+          (<literal>swapon device</literal>). The installer (or rather,
+          the build actions that it may spawn) may need quite a bit of
+          RAM, depending on your configuration.
+        </para>
+        <programlisting>
+# swapon /dev/sda2
+</programlisting>
+      </listitem>
+      <listitem>
+        <para>
+          You now need to create a file
+          <literal>/mnt/etc/nixos/configuration.nix</literal> that
+          specifies the intended configuration of the system. This is
+          because NixOS has a <emphasis>declarative</emphasis>
+          configuration model: you create or edit a description of the
+          desired configuration of your system, and then NixOS takes
+          care of making it happen. The syntax of the NixOS
+          configuration file is described in
+          <xref linkend="sec-configuration-syntax" />, while a list of
+          available configuration options appears in
+          <xref linkend="ch-options" />. A minimal example is shown in
+          <link linkend="ex-config">Example: NixOS Configuration</link>.
+        </para>
+        <para>
+          The command <literal>nixos-generate-config</literal> can
+          generate an initial configuration file for you:
+        </para>
+        <programlisting>
+# nixos-generate-config --root /mnt
+</programlisting>
+        <para>
+          You should then edit
+          <literal>/mnt/etc/nixos/configuration.nix</literal> to suit
+          your needs:
+        </para>
+        <programlisting>
+# nano /mnt/etc/nixos/configuration.nix
+</programlisting>
+        <para>
+          If you’re using the graphical ISO image, other editors may be
+          available (such as <literal>vim</literal>). If you have
+          network access, you can also install other editors – for
+          instance, you can install Emacs by running
+          <literal>nix-env -f '&lt;nixpkgs&gt;' -iA emacs</literal>.
+        </para>
+        <variablelist>
+          <varlistentry>
+            <term>
+              BIOS systems
+            </term>
+            <listitem>
+              <para>
+                You <emphasis>must</emphasis> set the option
+                <xref linkend="opt-boot.loader.grub.device" /> to
+                specify on which disk the GRUB boot loader is to be
+                installed. Without it, NixOS cannot boot.
+              </para>
+            </listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>
+              UEFI systems
+            </term>
+            <listitem>
+              <para>
+                You <emphasis>must</emphasis> set the option
+                <xref linkend="opt-boot.loader.systemd-boot.enable" />
+                to <literal>true</literal>.
+                <literal>nixos-generate-config</literal> should do this
+                automatically for new configurations when booted in UEFI
+                mode.
+              </para>
+              <para>
+                You may want to look at the options starting with
+                <link linkend="opt-boot.loader.efi.canTouchEfiVariables"><literal>boot.loader.efi</literal></link>
+                and
+                <link linkend="opt-boot.loader.systemd-boot.enable"><literal>boot.loader.systemd-boot</literal></link>
+                as well.
+              </para>
+            </listitem>
+          </varlistentry>
+        </variablelist>
+        <para>
+          If there are other operating systems running on the machine
+          before installing NixOS, the
+          <xref linkend="opt-boot.loader.grub.useOSProber" /> option can
+          be set to <literal>true</literal> to automatically add them to
+          the grub menu.
+        </para>
+        <para>
+          If you need to configure networking for your machine the
+          configuration options are described in
+          <xref linkend="sec-networking" />. In particular, while wifi
+          is supported on the installation image, it is not enabled by
+          default in the configuration generated by
+          <literal>nixos-generate-config</literal>.
+        </para>
+        <para>
+          Another critical option is <literal>fileSystems</literal>,
+          specifying the file systems that need to be mounted by NixOS.
+          However, you typically don’t need to set it yourself, because
+          <literal>nixos-generate-config</literal> sets it automatically
+          in
+          <literal>/mnt/etc/nixos/hardware-configuration.nix</literal>
+          from your currently mounted file systems. (The configuration
+          file <literal>hardware-configuration.nix</literal> is included
+          from <literal>configuration.nix</literal> and will be
+          overwritten by future invocations of
+          <literal>nixos-generate-config</literal>; thus, you generally
+          should not modify it.) Additionally, you may want to look at
+          <link xlink:href="https://github.com/NixOS/nixos-hardware">Hardware
+          configuration for known-hardware</link> at this point or after
+          installation.
+        </para>
+        <note>
+          <para>
+            Depending on your hardware configuration or type of file
+            system, you may need to set the option
+            <literal>boot.initrd.kernelModules</literal> to include the
+            kernel modules that are necessary for mounting the root file
+            system, otherwise the installed system will not be able to
+            boot. (If this happens, boot from the installation media
+            again, mount the target file system on
+            <literal>/mnt</literal>, fix
+            <literal>/mnt/etc/nixos/configuration.nix</literal> and
+            rerun <literal>nixos-install</literal>.) In most cases,
+            <literal>nixos-generate-config</literal> will figure out the
+            required modules.
+          </para>
+        </note>
+      </listitem>
+      <listitem>
+        <para>
+          Do the installation:
+        </para>
+        <programlisting>
+# nixos-install
+</programlisting>
+        <para>
+          This will install your system based on the configuration you
+          provided. If anything fails due to a configuration problem or
+          any other issue (such as a network outage while downloading
+          binaries from the NixOS binary cache), you can re-run
+          <literal>nixos-install</literal> after fixing your
+          <literal>configuration.nix</literal>.
+        </para>
+        <para>
+          As the last step, <literal>nixos-install</literal> will ask
+          you to set the password for the <literal>root</literal> user,
+          e.g.
+        </para>
+        <programlisting>
+setting root password...
+New password: ***
+Retype new password: ***
+</programlisting>
+        <note>
+          <para>
+            For unattended installations, it is possible to use
+            <literal>nixos-install --no-root-passwd</literal> in order
+            to disable the password prompt entirely.
+          </para>
+        </note>
+      </listitem>
+      <listitem>
+        <para>
+          If everything went well:
+        </para>
+        <programlisting>
+# reboot
+</programlisting>
+      </listitem>
+      <listitem>
+        <para>
+          You should now be able to boot into the installed NixOS. The
+          GRUB boot menu shows a list of <emphasis>available
+          configurations</emphasis> (initially just one). Every time you
+          change the NixOS configuration (see
+          <link linkend="sec-changing-config">Changing
+          Configuration</link>), a new item is added to the menu. This
+          allows you to easily roll back to a previous configuration if
+          something goes wrong.
+        </para>
+        <para>
+          You should log in and change the <literal>root</literal>
+          password with <literal>passwd</literal>.
+        </para>
+        <para>
+          You’ll probably want to create some user accounts as well,
+          which can be done with <literal>useradd</literal>:
+        </para>
+        <programlisting>
+$ useradd -c 'Eelco Dolstra' -m eelco
+$ passwd eelco
+</programlisting>
+        <para>
+          You may also want to install some software. This will be
+          covered in <xref linkend="sec-package-management" />.
+        </para>
+      </listitem>
+    </orderedlist>
+  </section>
+  <section xml:id="sec-installation-summary">
+    <title>Installation summary</title>
+    <para>
+      To summarise, <link linkend="ex-install-sequence">Example:
+      Commands for Installing NixOS on
+      <literal>/dev/sda</literal></link> shows a typical sequence of
+      commands for installing NixOS on an empty hard drive (here
+      <literal>/dev/sda</literal>). <link linkend="ex-config">Example:
+      NixOS Configuration</link> shows a corresponding configuration Nix
+      expression.
+    </para>
+    <anchor xml:id="ex-partition-scheme-MBR" />
+    <para>
+      <emphasis role="strong">Example: Example partition schemes for
+      NixOS on <literal>/dev/sda</literal> (MBR)</emphasis>
+    </para>
+    <programlisting>
+# parted /dev/sda -- mklabel msdos
+# parted /dev/sda -- mkpart primary 1MiB -8GiB
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+</programlisting>
+    <anchor xml:id="ex-partition-scheme-UEFI" />
+    <para>
+      <emphasis role="strong">Example: Example partition schemes for
+      NixOS on <literal>/dev/sda</literal> (UEFI)</emphasis>
+    </para>
+    <programlisting>
+# parted /dev/sda -- mklabel gpt
+# parted /dev/sda -- mkpart primary 512MiB -8GiB
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+# parted /dev/sda -- set 3 esp on
+</programlisting>
+    <anchor xml:id="ex-install-sequence" />
+    <para>
+      <emphasis role="strong">Example: Commands for Installing NixOS on
+      <literal>/dev/sda</literal></emphasis>
+    </para>
+    <para>
+      With a partitioned disk.
+    </para>
+    <programlisting>
+# mkfs.ext4 -L nixos /dev/sda1
+# mkswap -L swap /dev/sda2
+# swapon /dev/sda2
+# mkfs.fat -F 32 -n boot /dev/sda3        # (for UEFI systems only)
+# mount /dev/disk/by-label/nixos /mnt
+# mkdir -p /mnt/boot                      # (for UEFI systems only)
+# mount /dev/disk/by-label/boot /mnt/boot # (for UEFI systems only)
+# nixos-generate-config --root /mnt
+# nano /mnt/etc/nixos/configuration.nix
+# nixos-install
+# reboot
+</programlisting>
+    <anchor xml:id="ex-config" />
+    <para>
+      <emphasis role="strong">Example: NixOS Configuration</emphasis>
+    </para>
+    <programlisting>
+{ config, pkgs, ... }: {
+  imports = [
+    # Include the results of the hardware scan.
+    ./hardware-configuration.nix
+  ];
+
+  boot.loader.grub.device = &quot;/dev/sda&quot;;   # (for BIOS systems only)
+  boot.loader.systemd-boot.enable = true; # (for UEFI systems only)
+
+  # Note: setting fileSystems is generally not
+  # necessary, since nixos-generate-config figures them out
+  # automatically in hardware-configuration.nix.
+  #fileSystems.&quot;/&quot;.device = &quot;/dev/disk/by-label/nixos&quot;;
+
+  # Enable the OpenSSH server.
+  services.sshd.enable = true;
+}
+</programlisting>
+  </section>
+  <section xml:id="sec-installation-additional-notes">
+    <title>Additional installation notes</title>
+    <xi:include href="installing-usb.section.xml" />
+    <xi:include href="installing-pxe.section.xml" />
+    <xi:include href="installing-virtualbox-guest.section.xml" />
+    <xi:include href="installing-from-other-distro.section.xml" />
+    <xi:include href="installing-behind-a-proxy.section.xml" />
+  </section>
+</chapter>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
index c74d850b2c62f..edebd92b327a6 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2009.section.xml
@@ -1684,13 +1684,17 @@ CREATE ROLE postgres LOGIN SUPERUSER;
       </listitem>
       <listitem>
         <para>
-          The notmuch package move its emacs-related binaries and emacs
+          The notmuch package moves its emacs-related binaries and emacs
           lisp files to a separate output. They're not part of the
           default <literal>out</literal> output anymore - if you relied
           on the <literal>notmuch-emacs-mua</literal> binary or the
           emacs lisp files, access them via the
-          <literal>notmuch.emacs</literal> output. Device tree overlay
-          support was improved in
+          <literal>notmuch.emacs</literal> output.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Device tree overlay support was improved in
           <link xlink:href="https://github.com/NixOS/nixpkgs/pull/79370">#79370</link>
           and now uses
           <link xlink:href="options.html#opt-hardware.deviceTree.kernelPackage">hardware.deviceTree.kernelPackage</link>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index 1b0371a0179a8..f1d803136aa00 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -1,5 +1,5 @@
 <section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-21.11">
-  <title>Release 21.11 (“?”, 2021.11/??)</title>
+  <title>Release 21.11 (“Porcupine”, 2021.11/??)</title>
   <para>
     In addition to numerous new and upgraded packages, this release has
     the following highlights:
@@ -17,6 +17,21 @@
     <itemizedlist>
       <listitem>
         <para>
+          Nix has been updated to version 2.4, reference its
+          <link xlink:href="https://discourse.nixos.org/t/nix-2-4-released/15822">release
+          notes</link> for more information on what has changed. The
+          previous version of Nix, 2.3.16, remains available for the
+          time being in the <literal>nix_2_3</literal> package.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>iptables</literal> now uses
+          <literal>nf_tables</literal> backend.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           PHP now defaults to PHP 8.0, updated from 7.4.
         </para>
       </listitem>
@@ -37,6 +52,126 @@
           PostgreSQL now defaults to major version 13.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          spark now defaults to spark 3, updated from 2. A
+          <link xlink:href="https://spark.apache.org/docs/latest/core-migration-guide.html#upgrading-from-core-24-to-30">migration
+          guide</link> is available.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Improvements have been made to the Hadoop module and package:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              HDFS and YARN now support production-ready highly
+              available deployments with automatic failover.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Hadoop now defaults to Hadoop 3, updated from 2.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              JournalNode, ZKFS and HTTPFS services have been added.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          Activation scripts can now opt int to be run when running
+          <literal>nixos-rebuild dry-activate</literal> and detect the
+          dry activation by reading <literal>$NIXOS_ACTION</literal>.
+          This allows activation scripts to output what they would
+          change if the activation was really run. The users/modules
+          activation script supports this and outputs some of is
+          actions.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          KDE Plasma now finally works on Wayland.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          bash now defaults to major version 5.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Systemd was updated to version 249 (from 247).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Pantheon desktop has been updated to version 6. Due to changes
+          of screen locker, if locking doesn’t work for you, please try
+          <literal>gsettings set org.gnome.desktop.lockdown disable-lock-screen false</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>kubernetes-helm</literal> now defaults to 3.7.0,
+          which introduced some breaking changes to the experimental OCI
+          manifest format. See
+          <link xlink:href="https://github.com/helm/community/blob/main/hips/hip-0006.md">HIP
+          6</link> for more details. <literal>helmfile</literal> also
+          defaults to 0.141.0, which is the minimum compatible version.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          GNOME has been upgraded to 41. Please take a look at their
+          <link xlink:href="https://help.gnome.org/misc/release-notes/41.0/">Release
+          Notes</link> for details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          LXD support was greatly improved:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              building LXD images from configurations is now directly
+              possible with just nixpkgs
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              hydra is now building nixOS LXD images that can be used
+              standalone with full nixos-rebuild support
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          OpenSSH was updated to version 8.8p1
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              This breaks connections to old SSH daemons as ssh-rsa host
+              keys and ssh-rsa public keys that were signed with SHA-1
+              are disabled by default now
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              These can be re-enabled, see the
+              <link xlink:href="https://www.openssh.com/txt/release-8.8">OpenSSH
+              changelog</link> for details
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-new-services">
@@ -55,7 +190,15 @@
         <para>
           <link xlink:href="https://github.com/xrelkd/clipcat/">clipcat</link>,
           an X11 clipboard manager written in Rust. Available at
-          [services.clipcat](options.html#o pt-services.clipcat.enable).
+          <link xlink:href="options.html#opt-services.clipcat.enable">services.clipcat</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/dexidp/dex">dex</link>,
+          an OpenID Connect (OIDC) identity and OAuth 2.0 provider.
+          Available at
+          <link xlink:href="options.html#opt-services.dex.enable">services.dex</link>.
         </para>
       </listitem>
       <listitem>
@@ -67,6 +210,14 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://github.com/jitsi/jibri">Jibri</link>,
+          a service for recording or streaming a Jitsi Meet conference.
+          Available as
+          <link xlink:href="options.html#opt-services.jibri.enable">services.jibri</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://www.isc.org/kea/">Kea</link>, ISCs
           2nd generation DHCP and DDNS server suite. Available at
           <link xlink:href="options.html#opt-services.kea">services.kea</link>.
@@ -74,6 +225,21 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://owncast.online/">owncast</link>,
+          self-hosted video live streaming solution. Available at
+          <link xlink:href="options.html#opt-services.owncast">services.owncast</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://joinpeertube.org/">PeerTube</link>,
+          developed by Framasoft, is the free and decentralized
+          alternative to video platforms. Available at
+          <link xlink:href="options.html#opt-services.peertube">services.peertube</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://sr.ht">sourcehut</link>, a
           collection of tools useful for software development. Available
           as
@@ -105,6 +271,13 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://github.com/evilsocket/opensnitch">opensnitch</link>,
+          an application firewall. Available as
+          <link linkend="opt-services.opensnitch.enable">services.opensnitch</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://www.snapraid.it/">snapraid</link>, a
           backup program for disk arrays. Available as
           <link linkend="opt-snapraid.enable">snapraid</link>.
@@ -171,8 +344,6 @@
           <link linkend="opt-services.isso.enable">isso</link>
         </para>
       </listitem>
-    </itemizedlist>
-    <itemizedlist spacing="compact">
       <listitem>
         <para>
           <link xlink:href="https://www.navidrome.org/">navidrome</link>,
@@ -181,8 +352,6 @@
           <link linkend="opt-services.navidrome.enable">navidrome</link>.
         </para>
       </listitem>
-    </itemizedlist>
-    <itemizedlist>
       <listitem>
         <para>
           <link xlink:href="https://docs.fluidd.xyz/">fluidd</link>, a
@@ -209,6 +378,14 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://wiki.servarr.com/prowlarr">prowlarr</link>,
+          an indexer manager/proxy built on the popular arr .net/reactjs
+          base stack
+          <link linkend="opt-services.prowlarr.enable">services.prowlarr</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://sr.ht/~emersion/soju">soju</link>, a
           user-friendly IRC bouncer. Available as
           <link xlink:href="options.html#opt-services.soju.enable">services.soju</link>.
@@ -221,6 +398,97 @@
           <link linkend="opt-services.nats.enable">services.nats</link>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://git-scm.com">git</link>, a
+          distributed version control system. Available as
+          <link xlink:href="options.html#opt-programs.git.enable">programs.git</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>,
+          a service which parses incoming
+          <link xlink:href="https://dmarc.org/">DMARC</link> reports and
+          stores or sends them to a downstream service for further
+          analysis. Documented in
+          <link linkend="module-services-parsedmarc">its manual
+          entry</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://spark.apache.org/">spark</link>, a
+          unified analytics engine for large-scale data processing.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/JoseExposito/touchegg">touchegg</link>,
+          a multi-touch gesture recognizer. Available as
+          <link linkend="opt-services.touchegg.enable">services.touchegg</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/pantheon-tweaks/pantheon-tweaks">pantheon-tweaks</link>,
+          an unofficial system settings panel for Pantheon. Available as
+          <link linkend="opt-programs.pantheon-tweaks.enable">programs.pantheon-tweaks</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/DanielOgorchock/joycond">joycond</link>,
+          a service that uses <literal>hid-nintendo</literal> to provide
+          nintendo joycond pairing and better nintendo switch pro
+          controller support.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/opensvc/multipath-tools">multipath</link>,
+          the device mapper multipath (DM-MP) daemon. Available as
+          <link linkend="opt-services.multipath.enable">services.multipath</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://www.seafile.com/en/home/">seafile</link>,
+          an open source file syncing &amp; sharing software. Available
+          as
+          <link xlink:href="options.html#opt-services.seafile.enable">services.seafile</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/mchehab/rasdaemon">rasdaemon</link>,
+          a hardware error logging daemon. Available as
+          <link linkend="opt-hardware.rasdaemon.enable">hardware.rasdaemon</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>code-server</literal>-module now available
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/xmrig/xmrig">xmrig</link>,
+          a high performance, open source, cross platform RandomX,
+          KawPow, CryptoNight and AstroBWT unified CPU/GPU miner and
+          RandomX benchmark.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Auto nice daemons
+          <link xlink:href="https://github.com/Nefelim4ag/Ananicy">ananicy</link>
+          and
+          <link xlink:href="https://gitlab.com/ananicy-cpp/ananicy-cpp/">ananicy-cpp</link>.
+          Available as
+          <link linkend="opt-services.ananicy.enable">services.ananicy</link>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-incompatibilities">
@@ -228,6 +496,49 @@
     <itemizedlist>
       <listitem>
         <para>
+          The NixOS VM test framework,
+          <literal>pkgs.nixosTest</literal>/<literal>make-test-python.nix</literal>,
+          now requires detaching commands such as
+          <literal>succeed(&quot;foo &amp;&quot;)</literal> and
+          <literal>succeed(&quot;foo | xclip -i&quot;)</literal> to
+          close stdout. This can be done with a redirect such as
+          <literal>succeed(&quot;foo &gt;&amp;2 &amp;&quot;)</literal>.
+          This breaking change was necessitated by a race condition
+          causing tests to fail or hang. It applies to all methods that
+          invoke commands on the nodes, including
+          <literal>execute</literal>, <literal>succeed</literal>,
+          <literal>fail</literal>,
+          <literal>wait_until_succeeds</literal>,
+          <literal>wait_until_fails</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.wakeonlan</literal> option was removed,
+          and replaced with
+          <literal>networking.interfaces.&lt;name&gt;.wakeOnLan</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>security.wrappers</literal> option now requires
+          to always specify an owner, group and whether the
+          setuid/setgid bit should be set. This is motivated by the fact
+          that before NixOS 21.11, specifying either setuid or setgid
+          but not owner/group resulted in wrappers owned by
+          nobody/nogroup, which is unsafe.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Since <literal>iptables</literal> now uses
+          <literal>nf_tables</literal> backend and
+          <literal>ipset</literal> doesn’t support it, some applications
+          (ferm, shorewall, firehol) may have limited functionality.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The <literal>paperless</literal> module and package have been
           removed. All users should migrate to the successor
           <literal>paperless-ng</literal> instead. The Paperless project
@@ -313,7 +624,7 @@ Superuser created successfully.
       <listitem>
         <para>
           The <literal>staticjinja</literal> package has been upgraded
-          from 1.0.4 to 4.1.0
+          from 1.0.4 to 4.1.1
         </para>
       </listitem>
       <listitem>
@@ -340,6 +651,33 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="options.html#opt-users.users._name_.group">users.users.&lt;name&gt;.group</link>
+          no longer defaults to <literal>nogroup</literal>, which was
+          insecure. Out-of-tree modules are likely to require
+          adaptation: instead of
+        </para>
+        <programlisting language="bash">
+{
+  users.users.foo = {
+    isSystemUser = true;
+  };
+}
+</programlisting>
+        <para>
+          also create a group for your user:
+        </para>
+        <programlisting language="bash">
+{
+  users.users.foo = {
+    isSystemUser = true;
+    group = &quot;foo&quot;;
+  };
+  users.groups.foo = {};
+}
+</programlisting>
+      </listitem>
+      <listitem>
+        <para>
           <literal>services.geoip-updater</literal> was broken and has
           been replaced by
           <link xlink:href="options.html#opt-services.geoipupdate.enable">services.geoipupdate</link>.
@@ -347,6 +685,17 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          <literal>ihatemoney</literal> has been updated to version
+          5.1.1
+          (<link xlink:href="https://github.com/spiral-project/ihatemoney/blob/5.1.1/CHANGELOG.rst">release
+          notes</link>). If you serve ihatemoney by HTTP rather than
+          HTTPS, you must set
+          <link xlink:href="options.html#opt-services.ihatemoney.secureCookie">services.ihatemoney.secureCookie</link>
+          to <literal>false</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           PHP 7.3 is no longer supported due to upstream not supporting
           this version for the entire lifecycle of the 21.11 release.
         </para>
@@ -865,8 +1214,8 @@ Superuser created successfully.
       <listitem>
         <para>
           The <literal>varnish</literal> package was upgraded from 6.3.x
-          to 6.5.x. <literal>varnish60</literal> for the last LTS
-          release is also still available.
+          to 7.x. <literal>varnish60</literal> for the last LTS release
+          is also still available.
         </para>
       </listitem>
       <listitem>
@@ -909,6 +1258,124 @@ Superuser created successfully.
           file</link> format.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          The <literal>datadog-agent</literal>,
+          <literal>datadog-integrations-core</literal> and
+          <literal>datadog-process-agent</literal> packages were
+          upgraded from 6.11.2 to 7.30.2, git-2018-09-18 to 7.30.1 and
+          6.11.1 to 7.30.2, respectively. As a result
+          <literal>services.datadog-agent</literal> has had breaking
+          changes to the configuration file. For details, see the
+          <link xlink:href="https://github.com/DataDog/datadog-agent/blob/main/CHANGELOG.rst">upstream
+          changelog</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>opencv2</literal> no longer includes the non-free
+          libraries by default, and consequently
+          <literal>pfstools</literal> no longer includes OpenCV support
+          by default. Both packages now support an
+          <literal>enableUnfree</literal> option to re-enable this
+          functionality.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>services.xserver.displayManager.defaultSession = &quot;plasma5&quot;</literal>
+          does not work anymore, instead use either
+          <literal>&quot;plasma&quot;</literal> for the Plasma X11
+          session or <literal>&quot;plasmawayland&quot;</literal> for
+          the Plasma Wayland sesison.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>boot.kernelParams</literal> now only accepts one
+          command line parameter per string. This change is aimed to
+          reduce common mistakes like <quote>param = 12</quote>, which
+          would be parsed as 3 parameters.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>nix.daemonNiceLevel</literal> and
+          <literal>nix.daemonIONiceLevel</literal> have been removed in
+          favour of the new options
+          <link xlink:href="options.html#opt-nix.daemonCPUSchedPolicy"><literal>nix.daemonCPUSchedPolicy</literal></link>,
+          <link xlink:href="options.html#opt-nix.daemonIOSchedClass"><literal>nix.daemonIOSchedClass</literal></link>
+          and
+          <link xlink:href="options.html#opt-nix.daemonIOSchedPriority"><literal>nix.daemonIOSchedPriority</literal></link>.
+          Please refer to the options documentation and the
+          <literal>sched(7)</literal> and
+          <literal>ioprio_set(2)</literal> man pages for guidance on how
+          to use them.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>coursier</literal> package’s binary was renamed
+          from <literal>coursier</literal> to <literal>cs</literal>.
+          Completions which haven’t worked for a while should now work
+          with the renamed binary. To keep using
+          <literal>coursier</literal>, you can create a shell alias.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.mosquitto</literal> module has been
+          rewritten to support multiple listeners and per-listener
+          configuration. Module configurations from previous releases
+          will no longer work and must be updated.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>fluidsynth_1</literal> attribute has been
+          removed, as this legacy version is no longer needed in
+          nixpkgs. The actively maintained 2.x series is available as
+          <literal>fluidsynth</literal> unchanged.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Nextcloud 20 (<literal>pkgs.nextcloud20</literal>) has been
+          dropped because it was EOLed by upstream in 2021-10.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>virtualisation.pathsInNixDB</literal> option was
+          renamed
+          <link xlink:href="options.html#opt-virtualisation.additionalPaths"><literal>virtualisation.additionalPaths</literal></link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.ddclient.password</literal> option was
+          removed, and replaced with
+          <literal>services.ddclient.passwordFile</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The default GNAT version has been changed: The
+          <literal>gnat</literal> attribute now points to
+          <literal>gnat11</literal> instead of <literal>gnat9</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>retroArchCores</literal> has been removed. This means
+          that using <literal>nixpkgs.config.retroarch</literal> to
+          customize RetroArch cores is not supported anymore. Instead,
+          use package overrides, for example:
+          <literal>retroarch.override { cores = with libretro; [ citra snes9x ]; };</literal>.
+          Also, <literal>retroarchFull</literal> derivation is available
+          for those who want to have all RetroArch cores available.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-notable-changes">
@@ -916,6 +1383,60 @@ Superuser created successfully.
     <itemizedlist>
       <listitem>
         <para>
+          The linux kernel package infrastructure was moved out of
+          <literal>all-packages.nix</literal>, and restructured. Linux
+          related functions and attributes now live under the
+          <literal>pkgs.linuxKernel</literal> attribute set. In
+          particular the versioned <literal>linuxPackages_*</literal>
+          package sets (such as <literal>linuxPackages_5_4</literal>)
+          and kernels from <literal>pkgs</literal> were moved there and
+          now live under <literal>pkgs.linuxKernel.packages.*</literal>.
+          The unversioned ones (such as
+          <literal>linuxPackages_latest</literal>) remain untouched.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          In NixOS virtual machines (QEMU), the
+          <literal>virtualisation</literal> module has been updated with
+          new options:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              <link xlink:href="options.html#opt-virtualisation.forwardPorts"><literal>forwardPorts</literal></link>
+              to configure IPv4 port forwarding,
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <link xlink:href="options.html#opt-virtualisation.sharedDirectories"><literal>sharedDirectories</literal></link>
+              to set up shared host directories,
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <link xlink:href="options.html#opt-virtualisation.resolution"><literal>resolution</literal></link>
+              to set the screen resolution,
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <link xlink:href="options.html#opt-virtualisation.useNixStoreImage"><literal>useNixStoreImage</literal></link>
+              to use a disk image for the Nix store instead of 9P.
+            </para>
+          </listitem>
+        </itemizedlist>
+        <para>
+          In addition, the default
+          <link xlink:href="options.html#opt-virtualisation.msize"><literal>msize</literal></link>
+          parameter in 9P filesystems (including /nix/store and all
+          shared directories) has been increased to 16K for improved
+          performance.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The setting
           <link xlink:href="options.html#opt-services.openssh.logLevel"><literal>services.openssh.logLevel</literal></link>
           <literal>&quot;VERBOSE&quot;</literal>
@@ -935,6 +1456,14 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          The
+          <link xlink:href="options.html#opt-services.xserver.extraLayouts"><literal>services.xserver.extraLayouts</literal></link>
+          no longer cause additional rebuilds when a layout is added or
+          modified.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           Sway: The terminal emulator <literal>rxvt-unicode</literal> is
           no longer installed by default via
           <literal>programs.sway.extraPackages</literal>. The current
@@ -973,8 +1502,8 @@ Superuser created successfully.
           The wordpress module provides a new interface which allows to
           use different webservers with the new option
           <link xlink:href="options.html#opt-services.wordpress.webserver"><literal>services.wordpress.webserver</literal></link>.
-          Currently <literal>httpd</literal> and
-          <literal>nginx</literal> are supported. The definitions of
+          Currently <literal>httpd</literal>, <literal>caddy</literal>
+          and <literal>nginx</literal> are supported. The definitions of
           wordpress sites should now be set in
           <link xlink:href="options.html#opt-services.wordpress.sites"><literal>services.wordpress.sites</literal></link>.
         </para>
@@ -986,6 +1515,22 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          The dokuwiki module provides a new interface which allows to
+          use different webservers with the new option
+          <link xlink:href="options.html#opt-services.dokuwiki.webserver"><literal>services.dokuwiki.webserver</literal></link>.
+          Currently <literal>caddy</literal> and
+          <literal>nginx</literal> are supported. The definitions of
+          dokuwiki sites should now be set in
+          <link xlink:href="options.html#opt-services.dokuwiki.sites"><literal>services.dokuwiki.sites</literal></link>.
+        </para>
+        <para>
+          Sites definitions that use the old interface are automatically
+          migrated in the new option. This backward compatibility will
+          be removed in 22.05.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The order of NSS (host) modules has been brought in line with
           upstream recommendations:
         </para>
@@ -1058,6 +1603,73 @@ Superuser created successfully.
       <listitem>
         <para>
           The
+          <link xlink:href="options.html#opt-networking.wireless.enable">networking.wireless</link>
+          module (based on wpa_supplicant) has been heavily reworked,
+          solving a number of issues and adding useful features:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              The automatic discovery of wireless interfaces at boot has
+              been made reliable again (issues
+              <link xlink:href="https://github.com/NixOS/nixpkgs/issues/101963">#101963</link>,
+              <link xlink:href="https://github.com/NixOS/nixpkgs/issues/23196">#23196</link>).
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              WPA3 and Fast BSS Transition (802.11r) are now enabled by
+              default for all networks.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Secrets like pre-shared keys and passwords can now be
+              handled safely, meaning without including them in a
+              world-readable file
+              (<literal>wpa_supplicant.conf</literal> under /nix/store).
+              This is achieved by storing the secrets in a secured
+              <link xlink:href="options.html#opt-networking.wireless.environmentFile">environmentFile</link>
+              and referring to them though environment variables that
+              are expanded inside the configuration.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              With multiple interfaces declared, independent
+              wpa_supplicant daemons are started, one for each interface
+              (the services are named
+              <literal>wpa_supplicant-wlan0</literal>,
+              <literal>wpa_supplicant-wlan1</literal>, etc.).
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The generated <literal>wpa_supplicant.conf</literal> file
+              is now formatted for easier reading.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              A new
+              <link xlink:href="options.html#opt-networking.wireless.scanOnLowSignal">scanOnLowSignal</link>
+              option has been added to facilitate fast roaming between
+              access points (enabled by default).
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              A new
+              <link xlink:href="options.html#opt-networking.wireless.networks._name_.authProtocols">networks.&lt;name&gt;.authProtocols</link>
+              option has been added to change the authentication
+              protocols used when connecting to a network.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          The
           <link xlink:href="options.html#opt-networking.wireless.iwd.enable">networking.wireless.iwd</link>
           module has a new
           <link xlink:href="options.html#opt-networking.wireless.iwd.settings">networking.wireless.iwd.settings</link>
@@ -1067,6 +1679,23 @@ Superuser created successfully.
       <listitem>
         <para>
           The
+          <link xlink:href="options.html#opt-services.smokeping.host">services.smokeping.host</link>
+          option was added and defaulted to
+          <literal>localhost</literal>. Before,
+          <literal>smokeping</literal> listened to all interfaces by
+          default. NixOS defaults generally aim to provide
+          non-Internet-exposed defaults for databases and internal
+          monitoring tools, see e.g.
+          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/100192">#100192</link>.
+          Further, the systemd service for <literal>smokeping</literal>
+          got reworked defaults for increased operational stability, see
+          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/144127">PR
+          #144127</link> for details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
           <link xlink:href="options.html#opt-services.syncoid.enable">services.syncoid.enable</link>
           module now properly drops ZFS permissions after usage. Before
           it delegated permissions to whole pools instead of datasets
@@ -1104,6 +1733,24 @@ Superuser created successfully.
       </listitem>
       <listitem>
         <para>
+          MariaDB was upgraded from 10.5.x to 10.6.x. Please read the
+          <link xlink:href="https://mariadb.com/kb/en/changes-improvements-in-mariadb-106/">upstream
+          release notes</link> for changes and upgrade instructions.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The MariaDB C client library, also known as libmysqlclient or
+          mariadb-connector-c, was upgraded from 3.1.x to 3.2.x. While
+          this should hopefully not have any impact, this upgrade comes
+          with some changes to default behavior, so you might want to
+          review the
+          <link xlink:href="https://mariadb.com/kb/en/changes-and-improvements-in-mariadb-connector-c-32/">upstream
+          release notes</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           GNOME desktop environment now enables
           <literal>QGnomePlatform</literal> as the Qt platform theme,
           which should avoid crashes when opening file chooser dialogs
@@ -1121,6 +1768,225 @@ Superuser created successfully.
           rofi’s changelog</link>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          ipfs now defaults to not listening on you local network. This
+          setting was change as server providers won’t accept port
+          scanning on their private network. If you have several ipfs
+          instances running on a network you own, feel free to change
+          the setting <literal>ipfs.localDiscovery = true;</literal>.
+          localDiscovery enables different instances to discover each
+          other and share data.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>lua</literal> and <literal>luajit</literal>
+          interpreters have been patched to avoid looking into /usr/lib
+          directories, thus increasing the purity of the build.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Three new options,
+          <link linkend="opt-xdg.mime.addedAssociations">xdg.mime.addedAssociations</link>,
+          <link linkend="opt-xdg.mime.defaultApplications">xdg.mime.defaultApplications</link>,
+          and
+          <link linkend="opt-xdg.mime.removedAssociations">xdg.mime.removedAssociations</link>
+          have been added to the
+          <link linkend="opt-xdg.mime.enable">xdg.mime</link> module to
+          allow the configuration of
+          <literal>/etc/xdg/mimeapps.list</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Kopia was upgraded from 0.8.x to 0.9.x. Please read the
+          <link xlink:href="https://github.com/kopia/kopia/releases/tag/v0.9.0">upstream
+          release notes</link> for changes and upgrade instructions.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>systemd.network</literal> module has gained
+          support for the FooOverUDP link type.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>networking</literal> module has a new
+          <literal>networking.fooOverUDP</literal> option to configure
+          Foo-over-UDP encapsulations.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>networking.sits</literal> now supports Foo-over-UDP
+          encapsulation.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Changing systemd <literal>.socket</literal> units now restarts
+          them and stops the service that is activated by them.
+          Additionally, services with
+          <literal>stopOnChange = false</literal> don’t break anymore
+          when they are socket-activated.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>virtualisation.libvirtd</literal> module has been
+          refactored and updated with new options:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              <literal>virtualisation.libvirtd.qemu*</literal> options
+              (e.g.:
+              <literal>virtualisation.libvirtd.qemuRunAsRoot</literal>)
+              were moved to
+              <link xlink:href="options.html#opt-virtualisation.libvirtd.qemu"><literal>virtualisation.libvirtd.qemu</literal></link>
+              submodule,
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              software TPM1/TPM2 support (e.g.: Windows 11 guests)
+              (<link xlink:href="options.html#opt-virtualisation.libvirtd.qemu.swtpm"><literal>virtualisation.libvirtd.qemu.swtpm</literal></link>),
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              custom OVMF package (e.g.:
+              <literal>pkgs.OVMFFull</literal> with HTTP, CSM and Secure
+              Boot support)
+              (<link xlink:href="options.html#opt-virtualisation.libvirtd.qemu.ovmf.package"><literal>virtualisation.libvirtd.qemu.ovmf.package</literal></link>).
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>cawbird</literal> Twitter client now uses its own
+          API keys to count as different application than upstream
+          builds. This is done to evade application-level rate limiting.
+          While existing accounts continue to work, users may want to
+          remove and re-register their account in the client to enjoy a
+          better user experience and benefit from this change.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          A new option
+          <literal>services.prometheus.enableReload</literal> has been
+          added which can be enabled to reload the prometheus service
+          when its config file changes instead of restarting.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The option
+          <literal>services.prometheus.environmentFile</literal> has
+          been removed since it was causing
+          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/126083">issues</link>
+          and Prometheus now has native support for secret files, i.e.
+          <literal>basic_auth.password_file</literal> and
+          <literal>authorization.credentials_file</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Dokuwiki now supports caddy! However
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              the nginx option has been removed, in the new
+              configuration, please use the
+              <literal>dokuwiki.webserver = &quot;nginx&quot;</literal>
+              instead.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The <quote>${hostname}</quote> option has been deprecated,
+              please use
+              <literal>dokuwiki.sites = [ &quot;${hostname}&quot; ]</literal>
+              instead
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <link xlink:href="options.html#opt-services.unifi.enable">services.unifi</link>
+          module has been reworked, solving a number of issues. This
+          leads to several user facing changes:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              The <literal>services.unifi.dataDir</literal> option is
+              removed and the data is now always located under
+              <literal>/var/lib/unifi/data</literal>. This is done to
+              make better use of systemd state direcotiry and thus
+              making the service restart more reliable.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The unifi logs can now be found under:
+              <literal>/var/log/unifi</literal> instead of
+              <literal>/var/lib/unifi/logs</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The unifi run directory can now be found under:
+              <literal>/run/unifi</literal> instead of
+              <literal>/var/lib/unifi/run</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>security.pam.services.&lt;name&gt;.makeHomeDir</literal>
+          now uses <literal>umask=0077</literal> instead of
+          <literal>umask=0022</literal> when creating the home
+          directory.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Loki has had another release. Some default values have been
+          changed for the configuration and some configuration options
+          have been renamed. For more details, please check
+          <link xlink:href="https://grafana.com/docs/loki/latest/upgrading/#240">the
+          upgrade guide</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>julia</literal> now refers to
+          <literal>julia-stable</literal> instead of
+          <literal>julia-lts</literal>. In practice this means it has
+          been upgraded from <literal>1.0.4</literal> to
+          <literal>1.5.4</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          RetroArch has been upgraded from version
+          <literal>1.8.5</literal> to <literal>1.9.13.2</literal>. Since
+          the previous release was quite old, if you’re having issues
+          after the upgrade, please delete your
+          <literal>$XDG_CONFIG_HOME/retroarch/retroarch.cfg</literal>
+          file.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/installation/installation.xml b/nixos/doc/manual/installation/installation.xml
index cc18a9c6e9ffd..1d443bbd0ee16 100644
--- a/nixos/doc/manual/installation/installation.xml
+++ b/nixos/doc/manual/installation/installation.xml
@@ -11,7 +11,7 @@
   </para>
  </partintro>
  <xi:include href="../from_md/installation/obtaining.chapter.xml" />
- <xi:include href="installing.xml" />
+ <xi:include href="../from_md/installation/installing.chapter.xml" />
  <xi:include href="../from_md/installation/changing-config.chapter.xml" />
  <xi:include href="../from_md/installation/upgrading.chapter.xml" />
 </part>
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
new file mode 100644
index 0000000000000..def4f37fbcaa4
--- /dev/null
+++ b/nixos/doc/manual/installation/installing.chapter.md
@@ -0,0 +1,482 @@
+# Installing NixOS {#sec-installation}
+
+## Booting the system {#sec-installation-booting}
+
+NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
+installation is by and large the same as a BIOS installation. The
+differences are mentioned in the steps that follow.
+
+The installation media can be burned to a CD, or now more commonly,
+"burned" to a USB drive (see [](#sec-booting-from-usb)).
+
+The installation media contains a basic NixOS installation. When it's
+finished booting, it should have detected most of your hardware.
+
+The NixOS manual is available by running `nixos-help`.
+
+You are logged-in automatically as `nixos`. The `nixos` user account has
+an empty password so you can use `sudo` without a password:
+```ShellSession
+$ sudo -i
+```
+
+If you downloaded the graphical ISO image, you can run `systemctl
+start display-manager` to start the desktop environment. If you want
+to continue on the terminal, you can use `loadkeys` to switch to your
+preferred keyboard layout. (We even provide neo2 via `loadkeys de
+neo`!)
+
+If the text is too small to be legible, try `setfont ter-v32n` to
+increase the font size.
+
+To install over a serial port connect with `115200n8` (e.g.
+`picocom -b 115200 /dev/ttyUSB0`). When the bootloader lists boot
+entries, select the serial console boot entry.
+
+### Networking in the installer {#sec-installation-booting-networking}
+
+The boot process should have brought up networking (check `ip
+a`). Networking is necessary for the installer, since it will
+download lots of stuff (such as source tarballs or Nixpkgs channel
+binaries). It's best if you have a DHCP server on your network.
+Otherwise configure networking manually using `ifconfig`.
+
+On the graphical installer, you can configure the network, wifi
+included, through NetworkManager. Using the `nmtui` program, you can do
+so even in a non-graphical session. If you prefer to configure the
+network manually, disable NetworkManager with
+`systemctl stop NetworkManager`.
+
+On the minimal installer, NetworkManager is not available, so
+configuration must be perfomed manually. To configure the wifi, first
+start wpa_supplicant with `sudo systemctl start wpa_supplicant`, then
+run `wpa_cli`. For most home networks, you need to type in the following
+commands:
+
+```plain
+> add_network
+0
+> set_network 0 ssid "myhomenetwork"
+OK
+> set_network 0 psk "mypassword"
+OK
+> set_network 0 key_mgmt WPA-PSK
+OK
+> enable_network 0
+OK
+```
+
+For enterprise networks, for example *eduroam*, instead do:
+
+```plain
+> add_network
+0
+> set_network 0 ssid "eduroam"
+OK
+> set_network 0 identity "myname@example.com"
+OK
+> set_network 0 password "mypassword"
+OK
+> set_network 0 key_mgmt WPA-EAP
+OK
+> enable_network 0
+OK
+```
+
+When successfully connected, you should see a line such as this one
+
+```plain
+<3>CTRL-EVENT-CONNECTED - Connection to 32:85:ab:ef:24:5c completed [id=0 id_str=]
+```
+
+you can now leave `wpa_cli` by typing `quit`.
+
+If you would like to continue the installation from a different machine
+you can use activated SSH daemon. You need to copy your ssh key to
+either `/home/nixos/.ssh/authorized_keys` or
+`/root/.ssh/authorized_keys` (Tip: For installers with a modifiable
+filesystem such as the sd-card installer image a key can be manually
+placed by mounting the image on a different machine). Alternatively you
+must set a password for either `root` or `nixos` with `passwd` to be
+able to login.
+
+## Partitioning and formatting {#sec-installation-partitioning}
+
+The NixOS installer doesn't do any partitioning or formatting, so you
+need to do that yourself.
+
+The NixOS installer ships with multiple partitioning tools. The examples
+below use `parted`, but also provides `fdisk`, `gdisk`, `cfdisk`, and
+`cgdisk`.
+
+The recommended partition scheme differs depending if the computer uses
+*Legacy Boot* or *UEFI*.
+
+### UEFI (GPT) {#sec-installation-partitioning-UEFI}
+
+Here\'s an example partition scheme for UEFI, using `/dev/sda` as the
+device.
+
+::: {.note}
+You can safely ignore `parted`\'s informational message about needing to
+update /etc/fstab.
+:::
+
+1.  Create a *GPT* partition table.
+
+    ```ShellSession
+    # parted /dev/sda -- mklabel gpt
+    ```
+
+2.  Add the *root* partition. This will fill the disk except for the end
+    part, where the swap will live, and the space left in front (512MiB)
+    which will be used by the boot partition.
+
+    ```ShellSession
+    # parted /dev/sda -- mkpart primary 512MiB -8GiB
+    ```
+
+3.  Next, add a *swap* partition. The size required will vary according
+    to needs, here a 8GiB one is created.
+
+    ```ShellSession
+    # parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+    ```
+
+    ::: {.note}
+    The swap partition size rules are no different than for other Linux
+    distributions.
+    :::
+
+4.  Finally, the *boot* partition. NixOS by default uses the ESP (EFI
+    system partition) as its */boot* partition. It uses the initially
+    reserved 512MiB at the start of the disk.
+
+    ```ShellSession
+    # parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+    # parted /dev/sda -- set 3 esp on
+    ```
+
+Once complete, you can follow with
+[](#sec-installation-partitioning-formatting).
+
+### Legacy Boot (MBR) {#sec-installation-partitioning-MBR}
+
+Here\'s an example partition scheme for Legacy Boot, using `/dev/sda` as
+the device.
+
+::: {.note}
+You can safely ignore `parted`\'s informational message about needing to
+update /etc/fstab.
+:::
+
+1.  Create a *MBR* partition table.
+
+    ```ShellSession
+    # parted /dev/sda -- mklabel msdos
+    ```
+
+2.  Add the *root* partition. This will fill the the disk except for the
+    end part, where the swap will live.
+
+    ```ShellSession
+    # parted /dev/sda -- mkpart primary 1MiB -8GiB
+    ```
+
+3.  Finally, add a *swap* partition. The size required will vary
+    according to needs, here a 8GiB one is created.
+
+    ```ShellSession
+    # parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+    ```
+
+    ::: {.note}
+    The swap partition size rules are no different than for other Linux
+    distributions.
+    :::
+
+Once complete, you can follow with
+[](#sec-installation-partitioning-formatting).
+
+### Formatting {#sec-installation-partitioning-formatting}
+
+Use the following commands:
+
+-   For initialising Ext4 partitions: `mkfs.ext4`. It is recommended
+    that you assign a unique symbolic label to the file system using the
+    option `-L label`, since this makes the file system configuration
+    independent from device changes. For example:
+
+    ```ShellSession
+    # mkfs.ext4 -L nixos /dev/sda1
+    ```
+
+-   For creating swap partitions: `mkswap`. Again it's recommended to
+    assign a label to the swap partition: `-L label`. For example:
+
+    ```ShellSession
+    # mkswap -L swap /dev/sda2
+    ```
+
+-   **UEFI systems**
+
+    For creating boot partitions: `mkfs.fat`. Again it's recommended
+    to assign a label to the boot partition: `-n label`. For
+    example:
+
+    ```ShellSession
+    # mkfs.fat -F 32 -n boot /dev/sda3
+    ```
+
+-   For creating LVM volumes, the LVM commands, e.g., `pvcreate`,
+    `vgcreate`, and `lvcreate`.
+
+-   For creating software RAID devices, use `mdadm`.
+
+## Installing {#sec-installation-installing}
+
+1.  Mount the target file system on which NixOS should be installed on
+    `/mnt`, e.g.
+
+    ```ShellSession
+    # mount /dev/disk/by-label/nixos /mnt
+    ```
+
+2.  **UEFI systems**
+
+    Mount the boot file system on `/mnt/boot`, e.g.
+
+    ```ShellSession
+    # mkdir -p /mnt/boot
+    # mount /dev/disk/by-label/boot /mnt/boot
+    ```
+
+3.  If your machine has a limited amount of memory, you may want to
+    activate swap devices now (`swapon device`).
+    The installer (or rather, the build actions that it
+    may spawn) may need quite a bit of RAM, depending on your
+    configuration.
+
+    ```ShellSession
+    # swapon /dev/sda2
+    ```
+
+4.  You now need to create a file `/mnt/etc/nixos/configuration.nix`
+    that specifies the intended configuration of the system. This is
+    because NixOS has a *declarative* configuration model: you create or
+    edit a description of the desired configuration of your system, and
+    then NixOS takes care of making it happen. The syntax of the NixOS
+    configuration file is described in [](#sec-configuration-syntax),
+    while a list of available configuration options appears in
+    [](#ch-options). A minimal example is shown in
+    [Example: NixOS Configuration](#ex-config).
+
+    The command `nixos-generate-config` can generate an initial
+    configuration file for you:
+
+    ```ShellSession
+    # nixos-generate-config --root /mnt
+    ```
+
+    You should then edit `/mnt/etc/nixos/configuration.nix` to suit your
+    needs:
+
+    ```ShellSession
+    # nano /mnt/etc/nixos/configuration.nix
+    ```
+
+    If you're using the graphical ISO image, other editors may be
+    available (such as `vim`). If you have network access, you can also
+    install other editors -- for instance, you can install Emacs by
+    running `nix-env -f '<nixpkgs>' -iA emacs`.
+
+    BIOS systems
+
+    :   You *must* set the option [](#opt-boot.loader.grub.device) to
+        specify on which disk the GRUB boot loader is to be installed.
+        Without it, NixOS cannot boot.
+
+    UEFI systems
+
+    :   You *must* set the option [](#opt-boot.loader.systemd-boot.enable)
+        to `true`. `nixos-generate-config` should do this automatically
+        for new configurations when booted in UEFI mode.
+
+        You may want to look at the options starting with
+        [`boot.loader.efi`](#opt-boot.loader.efi.canTouchEfiVariables) and
+        [`boot.loader.systemd-boot`](#opt-boot.loader.systemd-boot.enable)
+        as well.
+
+    If there are other operating systems running on the machine before
+    installing NixOS, the [](#opt-boot.loader.grub.useOSProber)
+    option can be set to `true` to automatically add them to the grub
+    menu.
+
+    If you need to configure networking for your machine the
+    configuration options are described in [](#sec-networking). In
+    particular, while wifi is supported on the installation image, it is
+    not enabled by default in the configuration generated by
+    `nixos-generate-config`.
+
+    Another critical option is `fileSystems`, specifying the file
+    systems that need to be mounted by NixOS. However, you typically
+    don't need to set it yourself, because `nixos-generate-config` sets
+    it automatically in `/mnt/etc/nixos/hardware-configuration.nix` from
+    your currently mounted file systems. (The configuration file
+    `hardware-configuration.nix` is included from `configuration.nix`
+    and will be overwritten by future invocations of
+    `nixos-generate-config`; thus, you generally should not modify it.)
+    Additionally, you may want to look at [Hardware configuration for
+    known-hardware](https://github.com/NixOS/nixos-hardware) at this
+    point or after installation.
+
+    ::: {.note}
+    Depending on your hardware configuration or type of file system, you
+    may need to set the option `boot.initrd.kernelModules` to include
+    the kernel modules that are necessary for mounting the root file
+    system, otherwise the installed system will not be able to boot. (If
+    this happens, boot from the installation media again, mount the
+    target file system on `/mnt`, fix `/mnt/etc/nixos/configuration.nix`
+    and rerun `nixos-install`.) In most cases, `nixos-generate-config`
+    will figure out the required modules.
+    :::
+
+5.  Do the installation:
+
+    ```ShellSession
+    # nixos-install
+    ```
+
+    This will install your system based on the configuration you
+    provided. If anything fails due to a configuration problem or any
+    other issue (such as a network outage while downloading binaries
+    from the NixOS binary cache), you can re-run `nixos-install` after
+    fixing your `configuration.nix`.
+
+    As the last step, `nixos-install` will ask you to set the password
+    for the `root` user, e.g.
+
+    ```plain
+    setting root password...
+    New password: ***
+    Retype new password: ***
+    ```
+
+    ::: {.note}
+    For unattended installations, it is possible to use
+    `nixos-install --no-root-passwd` in order to disable the password
+    prompt entirely.
+    :::
+
+6.  If everything went well:
+
+    ```ShellSession
+    # reboot
+    ```
+
+7.  You should now be able to boot into the installed NixOS. The GRUB
+    boot menu shows a list of *available configurations* (initially just
+    one). Every time you change the NixOS configuration (see [Changing
+    Configuration](#sec-changing-config)), a new item is added to the
+    menu. This allows you to easily roll back to a previous
+    configuration if something goes wrong.
+
+    You should log in and change the `root` password with `passwd`.
+
+    You'll probably want to create some user accounts as well, which can
+    be done with `useradd`:
+
+    ```ShellSession
+    $ useradd -c 'Eelco Dolstra' -m eelco
+    $ passwd eelco
+    ```
+
+    You may also want to install some software. This will be covered in
+    [](#sec-package-management).
+
+## Installation summary {#sec-installation-summary}
+
+To summarise, [Example: Commands for Installing NixOS on `/dev/sda`](#ex-install-sequence)
+shows a typical sequence of commands for installing NixOS on an empty hard
+drive (here `/dev/sda`). [Example: NixOS Configuration](#ex-config) shows a
+corresponding configuration Nix expression.
+
+::: {#ex-partition-scheme-MBR .example}
+::: {.title}
+**Example: Example partition schemes for NixOS on `/dev/sda` (MBR)**
+:::
+```ShellSession
+# parted /dev/sda -- mklabel msdos
+# parted /dev/sda -- mkpart primary 1MiB -8GiB
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+```
+:::
+
+::: {#ex-partition-scheme-UEFI .example}
+::: {.title}
+**Example: Example partition schemes for NixOS on `/dev/sda` (UEFI)**
+:::
+```ShellSession
+# parted /dev/sda -- mklabel gpt
+# parted /dev/sda -- mkpart primary 512MiB -8GiB
+# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+# parted /dev/sda -- set 3 esp on
+```
+:::
+
+::: {#ex-install-sequence .example}
+::: {.title}
+**Example: Commands for Installing NixOS on `/dev/sda`**
+:::
+With a partitioned disk.
+
+```ShellSession
+# mkfs.ext4 -L nixos /dev/sda1
+# mkswap -L swap /dev/sda2
+# swapon /dev/sda2
+# mkfs.fat -F 32 -n boot /dev/sda3        # (for UEFI systems only)
+# mount /dev/disk/by-label/nixos /mnt
+# mkdir -p /mnt/boot                      # (for UEFI systems only)
+# mount /dev/disk/by-label/boot /mnt/boot # (for UEFI systems only)
+# nixos-generate-config --root /mnt
+# nano /mnt/etc/nixos/configuration.nix
+# nixos-install
+# reboot
+```
+:::
+
+::: {#ex-config .example}
+::: {.title}
+**Example: NixOS Configuration**
+:::
+```ShellSession
+{ config, pkgs, ... }: {
+  imports = [
+    # Include the results of the hardware scan.
+    ./hardware-configuration.nix
+  ];
+
+  boot.loader.grub.device = "/dev/sda";   # (for BIOS systems only)
+  boot.loader.systemd-boot.enable = true; # (for UEFI systems only)
+
+  # Note: setting fileSystems is generally not
+  # necessary, since nixos-generate-config figures them out
+  # automatically in hardware-configuration.nix.
+  #fileSystems."/".device = "/dev/disk/by-label/nixos";
+
+  # Enable the OpenSSH server.
+  services.sshd.enable = true;
+}
+```
+:::
+
+## Additional installation notes {#sec-installation-additional-notes}
+
+```{=docbook}
+<xi:include href="installing-usb.section.xml" />
+<xi:include href="installing-pxe.section.xml" />
+<xi:include href="installing-virtualbox-guest.section.xml" />
+<xi:include href="installing-from-other-distro.section.xml" />
+<xi:include href="installing-behind-a-proxy.section.xml" />
+```
diff --git a/nixos/doc/manual/installation/installing.xml b/nixos/doc/manual/installation/installing.xml
deleted file mode 100644
index 6eb097f243ab8..0000000000000
--- a/nixos/doc/manual/installation/installing.xml
+++ /dev/null
@@ -1,616 +0,0 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-            xmlns:xlink="http://www.w3.org/1999/xlink"
-            xmlns:xi="http://www.w3.org/2001/XInclude"
-            version="5.0"
-            xml:id="sec-installation">
- <title>Installing NixOS</title>
- <section xml:id="sec-installation-booting">
-  <title>Booting the system</title>
-
-  <para>
-   NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
-   installation is by and large the same as a BIOS installation. The
-   differences are mentioned in the steps that follow.
-  </para>
-
-  <para>
-   The installation media can be burned to a CD, or now more commonly, "burned"
-   to a USB drive (see <xref linkend="sec-booting-from-usb"/>).
-  </para>
-
-  <para>
-   The installation media contains a basic NixOS installation. When it’s
-   finished booting, it should have detected most of your hardware.
-  </para>
-
-  <para>
-   The NixOS manual is available by running <command>nixos-help</command>.
-  </para>
-
-  <para>
-   You are logged-in automatically as <literal>nixos</literal>.
-   The <literal>nixos</literal> user account has an empty password so you
-   can use <command>sudo</command> without a password.
-  </para>
-
-  <para>
-   If you downloaded the graphical ISO image, you can run <command>systemctl
-   start display-manager</command> to start the desktop environment. If you want to continue on the
-   terminal, you can use <command>loadkeys</command> to switch to your
-   preferred keyboard layout. (We even provide neo2 via <command>loadkeys de
-   neo</command>!)
-  </para>
-
-  <para>
-   If the text is too small to be legible, try <command>setfont ter-v32n</command>
-   to increase the font size.
-  </para>
-
-  <para>
-    To install over a serial port connect with <literal>115200n8</literal>
-    (e.g. <command>picocom -b 115200 /dev/ttyUSB0</command>). When the
-    bootloader lists boot entries, select the serial console boot entry.
-  </para>
-
-  <section xml:id="sec-installation-booting-networking">
-   <title>Networking in the installer</title>
-
-   <para>
-    The boot process should have brought up networking (check <command>ip
-    a</command>). Networking is necessary for the installer, since it will
-    download lots of stuff (such as source tarballs or Nixpkgs channel
-    binaries). It’s best if you have a DHCP server on your network. Otherwise
-    configure networking manually using <command>ifconfig</command>.
-   </para>
-
-   <para>
-    On the graphical installer, you can configure the network, wifi included,
-    through NetworkManager. Using the <command>nmtui</command> program, you
-    can do so even in a non-graphical session. If you prefer to configure the
-    network manually, disable NetworkManager with
-    <command>systemctl stop NetworkManager</command>.
-   </para>
-
-   <para>
-    On the minimal installer, NetworkManager is not available, so configuration
-    must be perfomed manually. To configure the wifi, first start wpa_supplicant
-    with <command>sudo systemctl start wpa_supplicant</command>, then run
-    <command>wpa_cli</command>. For most home networks, you need to type
-    in the following commands:
-    <programlisting>
-<prompt>&gt; </prompt>add_network
-0
-<prompt>&gt; </prompt>set_network 0 ssid "myhomenetwork"
-OK
-<prompt>&gt; </prompt>set_network 0 psk "mypassword"
-OK
-<prompt>&gt; </prompt>set_network 0 key_mgmt WPA-PSK
-OK
-<prompt>&gt; </prompt>enable_network 0
-OK
-    </programlisting>
-    For enterprise networks, for example <emphasis>eduroam</emphasis>, instead do:
-    <programlisting>
-<prompt>&gt; </prompt>add_network
-0
-<prompt>&gt; </prompt>set_network 0 ssid "eduroam"
-OK
-<prompt>&gt; </prompt>set_network 0 identity "myname@example.com"
-OK
-<prompt>&gt; </prompt>set_network 0 password "mypassword"
-OK
-<prompt>&gt; </prompt>set_network 0 key_mgmt WPA-EAP
-OK
-<prompt>&gt; </prompt>enable_network 0
-OK
-    </programlisting>
-    When successfully connected, you should see a line such as this one
-    <programlisting>
-&lt;3&gt;CTRL-EVENT-CONNECTED - Connection to 32:85:ab:ef:24:5c completed [id=0 id_str=]
-    </programlisting>
-    you can now leave <command>wpa_cli</command> by typing <command>quit</command>.
-   </para>
-
-   <para>
-    If you would like to continue the installation from a different machine you
-    can use activated SSH daemon. You need to copy your ssh key to either
-    <literal>/home/nixos/.ssh/authorized_keys</literal> or
-    <literal>/root/.ssh/authorized_keys</literal> (Tip: For installers with a
-    modifiable filesystem such as the sd-card installer image a key can be manually
-    placed by mounting the image on a different machine). Alternatively you must set
-    a password for either <literal>root</literal> or <literal>nixos</literal> with
-    <command>passwd</command> to be able to login.
-   </para>
-  </section>
- </section>
- <section xml:id="sec-installation-partitioning">
-  <title>Partitioning and formatting</title>
-
-  <para>
-   The NixOS installer doesn’t do any partitioning or formatting, so you need
-   to do that yourself.
-  </para>
-
-  <para>
-   The NixOS installer ships with multiple partitioning tools. The examples
-   below use <command>parted</command>, but also provides
-   <command>fdisk</command>, <command>gdisk</command>,
-   <command>cfdisk</command>, and <command>cgdisk</command>.
-  </para>
-
-  <para>
-   The recommended partition scheme differs depending if the computer uses
-   <emphasis>Legacy Boot</emphasis> or <emphasis>UEFI</emphasis>.
-  </para>
-
-  <section xml:id="sec-installation-partitioning-UEFI">
-   <title>UEFI (GPT)</title>
-
-   <para>
-    Here's an example partition scheme for UEFI, using
-    <filename>/dev/sda</filename> as the device.
-    <note>
-     <para>
-      You can safely ignore <command>parted</command>'s informational message
-      about needing to update /etc/fstab.
-     </para>
-    </note>
-   </para>
-
-   <para>
-    <orderedlist>
-     <listitem>
-      <para>
-       Create a <emphasis>GPT</emphasis> partition table.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mklabel gpt</screen>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       Add the <emphasis>root</emphasis> partition. This will fill the disk
-       except for the end part, where the swap will live, and the space left in
-       front (512MiB) which will be used by the boot partition.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary 512MiB -8GiB</screen>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       Next, add a <emphasis>swap</emphasis> partition. The size required will
-       vary according to needs, here a 8GiB one is created.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
-       <note>
-        <para>
-         The swap partition size rules are no different than for other Linux
-         distributions.
-        </para>
-       </note>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       Finally, the <emphasis>boot</emphasis> partition. NixOS by default uses
-       the ESP (EFI system partition) as its <emphasis>/boot</emphasis>
-       partition. It uses the initially reserved 512MiB at the start of the
-       disk.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-<prompt># </prompt>parted /dev/sda -- set 3 esp on</screen>
-      </para>
-     </listitem>
-    </orderedlist>
-   </para>
-
-   <para>
-    Once complete, you can follow with
-    <xref linkend="sec-installation-partitioning-formatting"/>.
-   </para>
-  </section>
-
-  <section xml:id="sec-installation-partitioning-MBR">
-   <title>Legacy Boot (MBR)</title>
-
-   <para>
-    Here's an example partition scheme for Legacy Boot, using
-    <filename>/dev/sda</filename> as the device.
-    <note>
-     <para>
-      You can safely ignore <command>parted</command>'s informational message
-      about needing to update /etc/fstab.
-     </para>
-    </note>
-   </para>
-
-   <para>
-    <orderedlist>
-     <listitem>
-      <para>
-       Create a <emphasis>MBR</emphasis> partition table.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mklabel msdos</screen>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       Add the <emphasis>root</emphasis> partition. This will fill the the disk
-       except for the end part, where the swap will live.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary 1MiB -8GiB</screen>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       Finally, add a <emphasis>swap</emphasis> partition. The size required
-       will vary according to needs, here a 8GiB one is created.
-<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
-       <note>
-        <para>
-         The swap partition size rules are no different than for other Linux
-         distributions.
-        </para>
-       </note>
-      </para>
-     </listitem>
-    </orderedlist>
-   </para>
-
-   <para>
-    Once complete, you can follow with
-    <xref linkend="sec-installation-partitioning-formatting"/>.
-   </para>
-  </section>
-
-  <section xml:id="sec-installation-partitioning-formatting">
-   <title>Formatting</title>
-
-   <para>
-    Use the following commands:
-    <itemizedlist>
-     <listitem>
-      <para>
-       For initialising Ext4 partitions: <command>mkfs.ext4</command>. It is
-       recommended that you assign a unique symbolic label to the file system
-       using the option <option>-L <replaceable>label</replaceable></option>,
-       since this makes the file system configuration independent from device
-       changes. For example:
-<screen>
-<prompt># </prompt>mkfs.ext4 -L nixos /dev/sda1</screen>
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       For creating swap partitions: <command>mkswap</command>. Again it’s
-       recommended to assign a label to the swap partition: <option>-L
-       <replaceable>label</replaceable></option>. For example:
-<screen>
-<prompt># </prompt>mkswap -L swap /dev/sda2</screen>
-      </para>
-     </listitem>
-     <listitem>
-      <variablelist>
-       <varlistentry>
-        <term>
-         UEFI systems
-        </term>
-        <listitem>
-         <para>
-          For creating boot partitions: <command>mkfs.fat</command>. Again
-          it’s recommended to assign a label to the boot partition:
-          <option>-n <replaceable>label</replaceable></option>. For example:
-<screen>
-<prompt># </prompt>mkfs.fat -F 32 -n boot /dev/sda3</screen>
-         </para>
-        </listitem>
-       </varlistentry>
-      </variablelist>
-     </listitem>
-     <listitem>
-      <para>
-       For creating LVM volumes, the LVM commands, e.g.,
-       <command>pvcreate</command>, <command>vgcreate</command>, and
-       <command>lvcreate</command>.
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       For creating software RAID devices, use <command>mdadm</command>.
-      </para>
-     </listitem>
-    </itemizedlist>
-   </para>
-  </section>
- </section>
- <section xml:id="sec-installation-installing">
-  <title>Installing</title>
-
-  <orderedlist>
-   <listitem>
-    <para>
-     Mount the target file system on which NixOS should be installed on
-     <filename>/mnt</filename>, e.g.
-<screen>
-<prompt># </prompt>mount /dev/disk/by-label/nixos /mnt
-</screen>
-    </para>
-   </listitem>
-   <listitem>
-    <variablelist>
-     <varlistentry>
-      <term>
-       UEFI systems
-      </term>
-      <listitem>
-       <para>
-        Mount the boot file system on <filename>/mnt/boot</filename>, e.g.
-<screen>
-<prompt># </prompt>mkdir -p /mnt/boot
-<prompt># </prompt>mount /dev/disk/by-label/boot /mnt/boot
-</screen>
-       </para>
-      </listitem>
-     </varlistentry>
-    </variablelist>
-   </listitem>
-   <listitem>
-    <para>
-     If your machine has a limited amount of memory, you may want to activate
-     swap devices now (<command>swapon
-     <replaceable>device</replaceable></command>). The installer (or rather,
-     the build actions that it may spawn) may need quite a bit of RAM,
-     depending on your configuration.
-<screen>
-<prompt># </prompt>swapon /dev/sda2</screen>
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     You now need to create a file
-     <filename>/mnt/etc/nixos/configuration.nix</filename> that specifies the
-     intended configuration of the system. This is because NixOS has a
-     <emphasis>declarative</emphasis> configuration model: you create or edit a
-     description of the desired configuration of your system, and then NixOS
-     takes care of making it happen. The syntax of the NixOS configuration file
-     is described in <xref linkend="sec-configuration-syntax"/>, while a list
-     of available configuration options appears in
-     <xref
-    linkend="ch-options"/>. A minimal example is shown in
-     <xref
-    linkend="ex-config"/>.
-    </para>
-    <para>
-     The command <command>nixos-generate-config</command> can generate an
-     initial configuration file for you:
-<screen>
-<prompt># </prompt>nixos-generate-config --root /mnt</screen>
-     You should then edit <filename>/mnt/etc/nixos/configuration.nix</filename>
-     to suit your needs:
-<screen>
-<prompt># </prompt>nano /mnt/etc/nixos/configuration.nix
-</screen>
-     If you’re using the graphical ISO image, other editors may be available
-     (such as <command>vim</command>). If you have network access, you can also
-     install other editors — for instance, you can install Emacs by running
-     <literal>nix-env -f '&lt;nixpkgs&gt;' -iA emacs</literal>.
-    </para>
-    <variablelist>
-     <varlistentry>
-      <term>
-       BIOS systems
-      </term>
-      <listitem>
-       <para>
-        You <emphasis>must</emphasis> set the option
-        <xref linkend="opt-boot.loader.grub.device"/> to specify on which disk
-        the GRUB boot loader is to be installed. Without it, NixOS cannot boot.
-       </para>
-      </listitem>
-     </varlistentry>
-     <varlistentry>
-      <term>
-       UEFI systems
-      </term>
-      <listitem>
-       <para>
-        You <emphasis>must</emphasis> set the option
-        <xref linkend="opt-boot.loader.systemd-boot.enable"/> to
-        <literal>true</literal>. <command>nixos-generate-config</command>
-        should do this automatically for new configurations when booted in UEFI
-        mode.
-       </para>
-       <para>
-        You may want to look at the options starting with
-        <option><link linkend="opt-boot.loader.efi.canTouchEfiVariables">boot.loader.efi</link></option>
-        and
-        <option><link linkend="opt-boot.loader.systemd-boot.enable">boot.loader.systemd-boot</link></option>
-        as well.
-       </para>
-      </listitem>
-     </varlistentry>
-    </variablelist>
-    <para>
-     If there are other operating systems running on the machine before
-     installing NixOS, the <xref linkend="opt-boot.loader.grub.useOSProber"/>
-     option can be set to <literal>true</literal> to automatically add them to
-     the grub menu.
-    </para>
-    <para>
-     If you need to configure networking for your machine the configuration
-     options are described in <xref linkend="sec-networking"/>. In particular,
-     while wifi is supported on the installation image, it is not enabled by
-     default in the configuration generated by
-     <command>nixos-generate-config</command>.
-    </para>
-    <para>
-     Another critical option is <option>fileSystems</option>, specifying the
-     file systems that need to be mounted by NixOS. However, you typically
-     don’t need to set it yourself, because
-     <command>nixos-generate-config</command> sets it automatically in
-     <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> from your
-     currently mounted file systems. (The configuration file
-     <filename>hardware-configuration.nix</filename> is included from
-     <filename>configuration.nix</filename> and will be overwritten by future
-     invocations of <command>nixos-generate-config</command>; thus, you
-     generally should not modify it.) Additionally, you may want to look at
-     <link xlink:href="https://github.com/NixOS/nixos-hardware">Hardware
-     configuration for known-hardware</link> at this point or after
-     installation.
-
-    </para>
-    <note>
-     <para>
-      Depending on your hardware configuration or type of file system, you may
-      need to set the option <option>boot.initrd.kernelModules</option> to
-      include the kernel modules that are necessary for mounting the root file
-      system, otherwise the installed system will not be able to boot. (If this
-      happens, boot from the installation media again, mount the target file
-      system on <filename>/mnt</filename>, fix
-      <filename>/mnt/etc/nixos/configuration.nix</filename> and rerun
-      <filename>nixos-install</filename>.) In most cases,
-      <command>nixos-generate-config</command> will figure out the required
-      modules.
-     </para>
-    </note>
-   </listitem>
-   <listitem>
-    <para>
-     Do the installation:
-<screen>
-<prompt># </prompt>nixos-install</screen>
-     This will install your system based on the configuration you provided.
-     If anything fails due to a configuration problem or any other issue
-     (such as a network outage while downloading binaries from the NixOS
-     binary cache), you can re-run <command>nixos-install</command> after
-     fixing your <filename>configuration.nix</filename>.
-    </para>
-    <para>
-     As the last step, <command>nixos-install</command> will ask you to set the
-     password for the <literal>root</literal> user, e.g.
-<screen>
-setting root password...
-New password: ***
-Retype new password: ***</screen>
-     <note>
-      <para>
-       For unattended installations, it is possible to use
-       <command>nixos-install --no-root-passwd</command> in order to disable
-       the password prompt entirely.
-      </para>
-     </note>
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     If everything went well:
-<screen>
-<prompt># </prompt>reboot</screen>
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     You should now be able to boot into the installed NixOS. The GRUB boot
-     menu shows a list of <emphasis>available configurations</emphasis>
-     (initially just one). Every time you change the NixOS configuration (see
-     <link
-        linkend="sec-changing-config">Changing Configuration</link>
-     ), a new item is added to the menu. This allows you to easily roll back to
-     a previous configuration if something goes wrong.
-    </para>
-    <para>
-     You should log in and change the <literal>root</literal> password with
-     <command>passwd</command>.
-    </para>
-    <para>
-     You’ll probably want to create some user accounts as well, which can be
-     done with <command>useradd</command>:
-<screen>
-<prompt>$ </prompt>useradd -c 'Eelco Dolstra' -m eelco
-<prompt>$ </prompt>passwd eelco</screen>
-    </para>
-    <para>
-     You may also want to install some software. This will be covered
-     in <xref linkend="sec-package-management" />.
-    </para>
-   </listitem>
-  </orderedlist>
- </section>
- <section xml:id="sec-installation-summary">
-  <title>Installation summary</title>
-
-  <para>
-   To summarise, <xref linkend="ex-install-sequence" /> shows a typical
-   sequence of commands for installing NixOS on an empty hard drive (here
-   <filename>/dev/sda</filename>). <xref linkend="ex-config"
-/> shows a
-   corresponding configuration Nix expression.
-  </para>
-
-  <example xml:id="ex-partition-scheme-MBR">
-   <title>Example partition schemes for NixOS on <filename>/dev/sda</filename> (MBR)</title>
-<screen language="commands">
-<prompt># </prompt>parted /dev/sda -- mklabel msdos
-<prompt># </prompt>parted /dev/sda -- mkpart primary 1MiB -8GiB
-<prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
-  </example>
-
-  <example xml:id="ex-partition-scheme-UEFI">
-   <title>Example partition schemes for NixOS on <filename>/dev/sda</filename> (UEFI)</title>
-<screen language="commands">
-<prompt># </prompt>parted /dev/sda -- mklabel gpt
-<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 esp on</screen>
-  </example>
-
-  <example xml:id="ex-install-sequence">
-   <title>Commands for Installing NixOS on <filename>/dev/sda</filename></title>
-   <para>
-    With a partitioned disk.
-<screen language="commands">
-<prompt># </prompt>mkfs.ext4 -L nixos /dev/sda1
-<prompt># </prompt>mkswap -L swap /dev/sda2
-<prompt># </prompt>swapon /dev/sda2
-<prompt># </prompt>mkfs.fat -F 32 -n boot /dev/sda3        # <lineannotation>(for UEFI systems only)</lineannotation>
-<prompt># </prompt>mount /dev/disk/by-label/nixos /mnt
-<prompt># </prompt>mkdir -p /mnt/boot                      # <lineannotation>(for UEFI systems only)</lineannotation>
-<prompt># </prompt>mount /dev/disk/by-label/boot /mnt/boot # <lineannotation>(for UEFI systems only)</lineannotation>
-<prompt># </prompt>nixos-generate-config --root /mnt
-<prompt># </prompt>nano /mnt/etc/nixos/configuration.nix
-<prompt># </prompt>nixos-install
-<prompt># </prompt>reboot</screen>
-   </para>
-  </example>
-
-  <example xml:id='ex-config'>
-   <title>NixOS Configuration</title>
-<programlisting>
-{ config, pkgs, ... }: {
-  imports = [
-    # Include the results of the hardware scan.
-    ./hardware-configuration.nix
-  ];
-
-  <xref linkend="opt-boot.loader.grub.device"/> = "/dev/sda";   # <lineannotation>(for BIOS systems only)</lineannotation>
-  <xref linkend="opt-boot.loader.systemd-boot.enable"/> = true; # <lineannotation>(for UEFI systems only)</lineannotation>
-
-  # Note: setting fileSystems is generally not
-  # necessary, since nixos-generate-config figures them out
-  # automatically in hardware-configuration.nix.
-  #<link linkend="opt-fileSystems._name_.device">fileSystems."/".device</link> = "/dev/disk/by-label/nixos";
-
-  # Enable the OpenSSH server.
-  services.sshd.enable = true;
-}
-</programlisting>
-  </example>
- </section>
- <section xml:id="sec-installation-additional-notes">
-  <title>Additional installation notes</title>
-
-  <xi:include href="../from_md/installation/installing-usb.section.xml" />
-
-  <xi:include href="../from_md/installation/installing-pxe.section.xml" />
-
-  <xi:include href="../from_md/installation/installing-virtualbox-guest.section.xml" />
-
-  <xi:include href="../from_md/installation/installing-from-other-distro.section.xml" />
-
-  <xi:include href="../from_md/installation/installing-behind-a-proxy.section.xml" />
- </section>
-</chapter>
diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml
index 8c34ea7458e64..0e0ea5d74b0b5 100644
--- a/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixos/doc/manual/man-nixos-rebuild.xml
@@ -553,6 +553,22 @@
 
    <varlistentry>
     <term>
+     <option>--use-substitutes</option>
+    </term>
+    <listitem>
+     <para>
+       When set, nixos-rebuild will add <option>--use-substitutes</option>
+       to each invocation of nix-copy-closure. This will only affect the
+       behavior of nixos-rebuild if <option>--target-host</option> or
+       <option>--build-host</option> is also set. This is useful when
+       the target-host connection to cache.nixos.org is faster than the
+       connection between hosts.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
      <option>--use-remote-sudo</option>
     </term>
     <listitem>
diff --git a/nixos/doc/manual/md-to-db.sh b/nixos/doc/manual/md-to-db.sh
index 6dd4b8c6e4194..e0274f5619c70 100755
--- a/nixos/doc/manual/md-to-db.sh
+++ b/nixos/doc/manual/md-to-db.sh
@@ -6,7 +6,7 @@
 # into DocBook files in the from_md folder.
 
 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
-pushd $DIR
+pushd "$DIR"
 
 # NOTE: Keep in sync with Nixpkgs manual (/doc/Makefile).
 # TODO: Remove raw-attribute when we can get rid of DocBook altogether.
@@ -29,7 +29,7 @@ mapfile -t MD_FILES < <(find . -type f -regex '.*\.md$')
 
 for mf in ${MD_FILES[*]}; do
   if [ "${mf: -11}" == ".section.md" ]; then
-    mkdir -p $(dirname "$OUT/$mf")
+    mkdir -p "$(dirname "$OUT/$mf")"
     OUTFILE="$OUT/${mf%".section.md"}.section.xml"
     pandoc "$mf" "${pandoc_flags[@]}" \
       -o "$OUTFILE"
@@ -37,7 +37,7 @@ for mf in ${MD_FILES[*]}; do
   fi
 
   if [ "${mf: -11}" == ".chapter.md" ]; then
-    mkdir -p $(dirname "$OUT/$mf")
+    mkdir -p "$(dirname "$OUT/$mf")"
     OUTFILE="$OUT/${mf%".chapter.md"}.chapter.xml"
     pandoc "$mf" "${pandoc_flags[@]}" \
       --top-level-division=chapter \
diff --git a/nixos/doc/manual/release-notes/rl-2009.section.md b/nixos/doc/manual/release-notes/rl-2009.section.md
index 48059ab07f5e3..79be2a56a54eb 100644
--- a/nixos/doc/manual/release-notes/rl-2009.section.md
+++ b/nixos/doc/manual/release-notes/rl-2009.section.md
@@ -578,7 +578,9 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `services.journald.rateLimitBurst` was updated from `1000` to `10000` to follow the new upstream systemd default.
 
-- The notmuch package move its emacs-related binaries and emacs lisp files to a separate output. They\'re not part of the default `out` output anymore - if you relied on the `notmuch-emacs-mua` binary or the emacs lisp files, access them via the `notmuch.emacs` output. Device tree overlay support was improved in [\#79370](https://github.com/NixOS/nixpkgs/pull/79370) and now uses [hardware.deviceTree.kernelPackage](options.html#opt-hardware.deviceTree.kernelPackage) instead of `hardware.deviceTree.base`. [hardware.deviceTree.overlays](options.html#opt-hardware.deviceTree.overlays) configuration was extended to support `.dts` files with symbols. Device trees can now be filtered by setting [hardware.deviceTree.filter](options.html#opt-hardware.deviceTree.filter) option.
+- The notmuch package moves its emacs-related binaries and emacs lisp files to a separate output. They\'re not part of the default `out` output anymore - if you relied on the `notmuch-emacs-mua` binary or the emacs lisp files, access them via the `notmuch.emacs` output.
+
+- Device tree overlay support was improved in [\#79370](https://github.com/NixOS/nixpkgs/pull/79370) and now uses [hardware.deviceTree.kernelPackage](options.html#opt-hardware.deviceTree.kernelPackage) instead of `hardware.deviceTree.base`. [hardware.deviceTree.overlays](options.html#opt-hardware.deviceTree.overlays) configuration was extended to support `.dts` files with symbols. Device trees can now be filtered by setting [hardware.deviceTree.filter](options.html#opt-hardware.deviceTree.filter) option.
 
 - The default output of `buildGoPackage` is now `$out` instead of `$bin`.
 
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 3df77d21d8276..275ee7142d0ec 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -1,4 +1,4 @@
-# Release 21.11 (“?”, 2021.11/??) {#sec-release-21.11}
+# Release 21.11 (“Porcupine”, 2021.11/??) {#sec-release-21.11}
 
 In addition to numerous new and upgraded packages, this release has the following highlights:
 
@@ -6,6 +6,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 ## Highlights {#sec-release-21.11-highlights}
 
+- Nix has been updated to version 2.4, reference its [release notes](https://discourse.nixos.org/t/nix-2-4-released/15822) for more information on what has changed. The previous version of Nix, 2.3.16, remains available for the time being in the `nix_2_3` package.
+
+- `iptables` now uses `nf_tables` backend.
+
 - PHP now defaults to PHP 8.0, updated from 7.4.
 
 - kOps now defaults to 1.21.1, which uses containerd as the default runtime.
@@ -14,17 +18,56 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - PostgreSQL now defaults to major version 13.
 
+- spark now defaults to spark 3, updated from 2. A [migration guide](https://spark.apache.org/docs/latest/core-migration-guide.html#upgrading-from-core-24-to-30) is available.
+
+- Improvements have been made to the Hadoop module and package:
+  - HDFS and YARN now support production-ready highly available deployments with automatic failover.
+  - Hadoop now defaults to Hadoop 3, updated from 2.
+  - JournalNode, ZKFS and HTTPFS services have been added.
+
+- Activation scripts can now opt int to be run when running `nixos-rebuild dry-activate` and detect the dry activation by reading `$NIXOS_ACTION`.
+  This allows activation scripts to output what they would change if the activation was really run.
+  The users/modules activation script supports this and outputs some of is actions.
+
+- KDE Plasma now finally works on Wayland.
+
+- bash now defaults to major version 5.
+
+- Systemd was updated to version 249 (from 247).
+
+- Pantheon desktop has been updated to version 6. Due to changes of screen locker, if locking doesn't work for you, please try `gsettings set org.gnome.desktop.lockdown disable-lock-screen false`.
+
+- `kubernetes-helm` now defaults to 3.7.0, which introduced some breaking changes to the experimental OCI manifest format. See [HIP 6](https://github.com/helm/community/blob/main/hips/hip-0006.md) for more details.
+  `helmfile` also defaults to 0.141.0, which is the minimum compatible version.
+
+- GNOME has been upgraded to 41. Please take a look at their [Release Notes](https://help.gnome.org/misc/release-notes/41.0/) for details.
+
+- LXD support was greatly improved:
+  - building LXD images from configurations is now directly possible with just nixpkgs
+  - hydra is now building nixOS LXD images that can be used standalone with full nixos-rebuild support
+
+- OpenSSH was updated to version 8.8p1
+  - This breaks connections to old SSH daemons as ssh-rsa host keys and ssh-rsa public keys that were signed with SHA-1 are disabled by default now
+  - These can be re-enabled, see the [OpenSSH changelog](https://www.openssh.com/txt/release-8.8) for details
+
 ## New Services {#sec-release-21.11-new-services}
 
 - [btrbk](https://digint.ch/btrbk/index.html), a backup tool for btrfs subvolumes, taking advantage of btrfs specific capabilities to create atomic snapshots and transfer them incrementally to your backup locations. Available as [services.btrbk](options.html#opt-services.brtbk.instances).
 
-- [clipcat](https://github.com/xrelkd/clipcat/), an X11 clipboard manager written in Rust. Available at [services.clipcat](options.html#o
-pt-services.clipcat.enable).
+- [clipcat](https://github.com/xrelkd/clipcat/), an X11 clipboard manager written in Rust. Available at [services.clipcat](options.html#opt-services.clipcat.enable).
+
+- [dex](https://github.com/dexidp/dex), an OpenID Connect (OIDC) identity and OAuth 2.0 provider. Available at [services.dex](options.html#opt-services.dex.enable).
 
 - [geoipupdate](https://github.com/maxmind/geoipupdate), a GeoIP database updater from MaxMind. Available as [services.geoipupdate](options.html#opt-services.geoipupdate.enable).
 
+- [Jibri](https://github.com/jitsi/jibri), a service for recording or streaming a Jitsi Meet conference. Available as [services.jibri](options.html#opt-services.jibri.enable).
+
 - [Kea](https://www.isc.org/kea/), ISCs 2nd generation DHCP and DDNS server suite. Available at [services.kea](options.html#opt-services.kea).
 
+- [owncast](https://owncast.online/), self-hosted video live streaming solution. Available at [services.owncast](options.html#opt-services.owncast).
+
+- [PeerTube](https://joinpeertube.org/), developed by Framasoft, is the free and decentralized alternative to video platforms. Available at [services.peertube](options.html#opt-services.peertube).
+
 - [sourcehut](https://sr.ht), a collection of tools useful for software development. Available as [services.sourcehut](options.html#opt-services.sourcehut.enable).
 
 - [ucarp](https://download.pureftpd.org/pub/ucarp/README), an userspace implementation of the Common Address Redundancy Protocol (CARP). Available as [networking.ucarp](options.html#opt-networking.ucarp.enable).
@@ -33,6 +76,8 @@ pt-services.clipcat.enable).
 
 - [vikunja](https://vikunja.io), a to-do list app. Available as [services.vikunja](#opt-services.vikunja.enable).
 
+- [opensnitch](https://github.com/evilsocket/opensnitch), an application firewall. Available as [services.opensnitch](#opt-services.opensnitch.enable).
+
 - [snapraid](https://www.snapraid.it/), a backup program for disk arrays.
   Available as [snapraid](#opt-snapraid.enable).
 
@@ -54,8 +99,8 @@ pt-services.clipcat.enable).
 - [isso](https://posativ.org/isso/), a commenting server similar to Disqus.
   Available as [isso](#opt-services.isso.enable)
 
-* [navidrome](https://www.navidrome.org/), a personal music streaming server with
-subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable).
+- [navidrome](https://www.navidrome.org/), a personal music streaming server with
+  subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable).
 
 - [fluidd](https://docs.fluidd.xyz/), a Klipper web interface for managing 3d printers using moonraker. Available as [fluidd](#opt-services.fluidd.enable).
 
@@ -63,12 +108,51 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 
 - [postfixadmin](https://postfixadmin.sourceforge.io/), a web based virtual user administration interface for Postfix mail servers. Available as [postfixadmin](#opt-services.postfixadmin.enable).
 
+- [prowlarr](https://wiki.servarr.com/prowlarr), an indexer manager/proxy built on the popular arr .net/reactjs base stack [services.prowlarr](#opt-services.prowlarr.enable).
+
 - [soju](https://sr.ht/~emersion/soju), a user-friendly IRC bouncer. Available as [services.soju](options.html#opt-services.soju.enable).
 
 - [nats](https://nats.io/), a high performance cloud and edge messaging system. Available as [services.nats](#opt-services.nats.enable).
 
+- [git](https://git-scm.com), a distributed version control system. Available as [programs.git](options.html#opt-programs.git.enable).
+
+- [parsedmarc](https://domainaware.github.io/parsedmarc/), a service
+  which parses incoming [DMARC](https://dmarc.org/) reports and stores
+  or sends them to a downstream service for further analysis.
+  Documented in [its manual entry](#module-services-parsedmarc).
+
+- [spark](https://spark.apache.org/), a unified analytics engine for large-scale data processing.
+
+- [touchegg](https://github.com/JoseExposito/touchegg), a multi-touch gesture recognizer. Available as [services.touchegg](#opt-services.touchegg.enable).
+
+- [pantheon-tweaks](https://github.com/pantheon-tweaks/pantheon-tweaks), an unofficial system settings panel for Pantheon. Available as [programs.pantheon-tweaks](#opt-programs.pantheon-tweaks.enable).
+
+- [joycond](https://github.com/DanielOgorchock/joycond), a service that uses `hid-nintendo` to provide nintendo joycond pairing and better nintendo switch pro controller support.
+
+- [multipath](https://github.com/opensvc/multipath-tools), the device mapper multipath (DM-MP) daemon. Available as [services.multipath](#opt-services.multipath.enable).
+
+- [seafile](https://www.seafile.com/en/home/), an open source file syncing & sharing software. Available as [services.seafile](options.html#opt-services.seafile.enable).
+
+- [rasdaemon](https://github.com/mchehab/rasdaemon), a hardware error logging daemon. Available as [hardware.rasdaemon](#opt-hardware.rasdaemon.enable).
+
+- `code-server`-module now available
+
+- [xmrig](https://github.com/xmrig/xmrig), a high performance, open source, cross platform RandomX, KawPow, CryptoNight and AstroBWT unified CPU/GPU miner and RandomX benchmark.
+
+- Auto nice daemons [ananicy](https://github.com/Nefelim4ag/Ananicy) and [ananicy-cpp](https://gitlab.com/ananicy-cpp/ananicy-cpp/). Available as [services.ananicy](#opt-services.ananicy.enable).
+
 ## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
 
+- The NixOS VM test framework, `pkgs.nixosTest`/`make-test-python.nix`, now requires detaching commands such as `succeed("foo &")` and `succeed("foo | xclip -i")` to close stdout.
+  This can be done with a redirect such as `succeed("foo >&2 &")`. This breaking change was necessitated by a race condition causing tests to fail or hang.
+  It applies to all methods that invoke commands on the nodes, including `execute`, `succeed`, `fail`, `wait_until_succeeds`, `wait_until_fails`.
+
+- The `services.wakeonlan` option was removed, and replaced with `networking.interfaces.<name>.wakeOnLan`.
+
+- The `security.wrappers` option now requires to always specify an owner, group and whether the setuid/setgid bit should be set.
+  This is motivated by the fact that before NixOS 21.11, specifying either setuid or setgid but not owner/group resulted in wrappers owned by nobody/nogroup, which is unsafe.
+
+- Since `iptables` now uses `nf_tables` backend and `ipset` doesn't support it, some applications (ferm, shorewall, firehol) may have limited functionality.
 
 - The `paperless` module and package have been removed. All users should migrate to the
   successor `paperless-ng` instead. The Paperless project [has been
@@ -76,48 +160,51 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
   and advises all users to use `paperless-ng` instead.
 
   Users can use the `services.paperless-ng` module as a replacement while noting the following incompatibilities:
-    - `services.paperless.ocrLanguages` has no replacement. Users should migrate to [`services.paperless-ng.extraConfig`](options.html#opt-services.paperless-ng.extraConfig) instead:
-     ```nix
-     {
-       services.paperless-ng.extraConfig = {
-         # Provide languages as ISO 639-2 codes
-         # separated by a plus (+) sign.
-         # https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
-         PAPERLESS_OCR_LANGUAGE = "deu+eng+jpn"; # German & English & Japanse
-       };
-     }
-     ```
-
-    - If you previously specified `PAPERLESS_CONSUME_MAIL_*` settings in
-      `services.paperless.extraConfig` you should remove those options now. You
-      now *must* define those settings in the admin interface of paperless-ng.
-
-    - Option `services.paperless.manage` no longer exists.
-      Use the script at `${services.paperless-ng.dataDir}/paperless-ng-manage` instead.
-      Note that this script only exists after the `paperless-ng` service has been
-      started at least once.
-
-    - After switching to the new system configuration you should run the Django
-      management command to reindex your documents and optionally create a user,
-      if you don't have one already.
-
-      To do so, enter the data directory (the value of
-      `services.paperless-ng.dataDir`, `/var/lib/paperless` by default), switch
-      to the paperless user and execute the management command like below:
-      ```
-      $ cd /var/lib/paperless
-      $ su paperless -s /bin/sh
-      $ ./paperless-ng-manage document_index reindex
-      # if not already done create a user account, paperless-ng requires a login
-      $ ./paperless-ng-manage createsuperuser
-      Username (leave blank to use 'paperless'): my-user-name
-      Email address: me@example.com
-      Password: **********
-      Password (again): **********
-      Superuser created successfully.
-      ```
-
-- The `staticjinja` package has been upgraded from 1.0.4 to 4.1.0
+
+  - `services.paperless.ocrLanguages` has no replacement. Users should migrate to [`services.paperless-ng.extraConfig`](options.html#opt-services.paperless-ng.extraConfig) instead:
+
+  ```nix
+  {
+    services.paperless-ng.extraConfig = {
+      # Provide languages as ISO 639-2 codes
+      # separated by a plus (+) sign.
+      # https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
+      PAPERLESS_OCR_LANGUAGE = "deu+eng+jpn"; # German & English & Japanse
+    };
+  }
+  ```
+
+  - If you previously specified `PAPERLESS_CONSUME_MAIL_*` settings in
+    `services.paperless.extraConfig` you should remove those options now. You
+    now _must_ define those settings in the admin interface of paperless-ng.
+
+  - Option `services.paperless.manage` no longer exists.
+    Use the script at `${services.paperless-ng.dataDir}/paperless-ng-manage` instead.
+    Note that this script only exists after the `paperless-ng` service has been
+    started at least once.
+
+  - After switching to the new system configuration you should run the Django
+    management command to reindex your documents and optionally create a user,
+    if you don't have one already.
+
+    To do so, enter the data directory (the value of
+    `services.paperless-ng.dataDir`, `/var/lib/paperless` by default), switch
+    to the paperless user and execute the management command like below:
+
+    ```
+    $ cd /var/lib/paperless
+    $ su paperless -s /bin/sh
+    $ ./paperless-ng-manage document_index reindex
+    # if not already done create a user account, paperless-ng requires a login
+    $ ./paperless-ng-manage createsuperuser
+    Username (leave blank to use 'paperless'): my-user-name
+    Email address: me@example.com
+    Password: **********
+    Password (again): **********
+    Superuser created successfully.
+    ```
+
+- The `staticjinja` package has been upgraded from 1.0.4 to 4.1.1
 
 - Firefox v91 does not support addons with invalid signature anymore. Firefox ESR needs to be used for nix addon support.
 
@@ -125,8 +212,29 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 
 - The `erigon` ethereum node has moved it's database location in `2021-08-03`, users upgrading must manually move their chaindata (see [release notes](https://github.com/ledgerwatch/erigon/releases/tag/v2021.08.03)).
 
+- [users.users.&lt;name&gt;.group](options.html#opt-users.users._name_.group) no longer defaults to `nogroup`, which was insecure. Out-of-tree modules are likely to require adaptation: instead of
+  ```nix
+  {
+    users.users.foo = {
+      isSystemUser = true;
+    };
+  }
+  ```
+  also create a group for your user:
+  ```nix
+  {
+    users.users.foo = {
+      isSystemUser = true;
+      group = "foo";
+    };
+    users.groups.foo = {};
+  }
+  ```
+
 - `services.geoip-updater` was broken and has been replaced by [services.geoipupdate](options.html#opt-services.geoipupdate.enable).
 
+- `ihatemoney` has been updated to version 5.1.1 ([release notes](https://github.com/spiral-project/ihatemoney/blob/5.1.1/CHANGELOG.rst)). If you serve ihatemoney by HTTP rather than HTTPS, you must set [services.ihatemoney.secureCookie](options.html#opt-services.ihatemoney.secureCookie) to `false`.
+
 - PHP 7.3 is no longer supported due to upstream not supporting this version for the entire lifecycle of the 21.11 release.
 
 - Those making use of `buildBazelPackage` will need to regenerate the fetch hashes (preferred), or set `fetchConfigured = false;`.
@@ -193,28 +301,32 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 * The `bitwarden_rs` packages and modules were renamed to `vaultwarden`
   [following upstream](https://github.com/dani-garcia/vaultwarden/discussions/1642). More specifically,
 
-  * `pkgs.bitwarden_rs`, `pkgs.bitwarden_rs-sqlite`, `pkgs.bitwarden_rs-mysql` and
+  - `pkgs.bitwarden_rs`, `pkgs.bitwarden_rs-sqlite`, `pkgs.bitwarden_rs-mysql` and
     `pkgs.bitwarden_rs-postgresql` were renamed to `pkgs.vaultwarden`, `pkgs.vaultwarden-sqlite`,
     `pkgs.vaultwarden-mysql` and `pkgs.vaultwarden-postgresql`, respectively.
-    * Old names are preserved as aliases for backwards compatibility, but may be removed in the future.
-    * The `bitwarden_rs` executable was also renamed to `vaultwarden` in all packages.
 
-  * `pkgs.bitwarden_rs-vault` was renamed to `pkgs.vaultwarden-vault`.
-    * `pkgs.bitwarden_rs-vault` is preserved as an alias for backwards compatibility, but may be removed in the future.
-    * The static files were moved from `/usr/share/bitwarden_rs` to `/usr/share/vaultwarden`.
+    - Old names are preserved as aliases for backwards compatibility, but may be removed in the future.
+    - The `bitwarden_rs` executable was also renamed to `vaultwarden` in all packages.
 
-  * The `services.bitwarden_rs` config module was renamed to `services.vaultwarden`.
-    * `services.bitwarden_rs` is preserved as an alias for backwards compatibility, but may be removed in the future.
+  - `pkgs.bitwarden_rs-vault` was renamed to `pkgs.vaultwarden-vault`.
 
-  * `systemd.services.bitwarden_rs`, `systemd.services.backup-bitwarden_rs` and `systemd.timers.backup-bitwarden_rs`
+    - `pkgs.bitwarden_rs-vault` is preserved as an alias for backwards compatibility, but may be removed in the future.
+    - The static files were moved from `/usr/share/bitwarden_rs` to `/usr/share/vaultwarden`.
+
+  - The `services.bitwarden_rs` config module was renamed to `services.vaultwarden`.
+
+    - `services.bitwarden_rs` is preserved as an alias for backwards compatibility, but may be removed in the future.
+
+  - `systemd.services.bitwarden_rs`, `systemd.services.backup-bitwarden_rs` and `systemd.timers.backup-bitwarden_rs`
     were renamed to `systemd.services.vaultwarden`, `systemd.services.backup-vaultwarden` and
     `systemd.timers.backup-vaultwarden`, respectively.
-    * Old names are preserved as aliases for backwards compatibility, but may be removed in the future.
 
-  * `users.users.bitwarden_rs` and `users.groups.bitwarden_rs` were renamed to `users.users.vaultwarden` and
+    - Old names are preserved as aliases for backwards compatibility, but may be removed in the future.
+
+  - `users.users.bitwarden_rs` and `users.groups.bitwarden_rs` were renamed to `users.users.vaultwarden` and
     `users.groups.vaultwarden`, respectively.
 
-  * The data directory remains located at `/var/lib/bitwarden_rs`, for backwards compatibility.
+  - The data directory remains located at `/var/lib/bitwarden_rs`, for backwards compatibility.
 
 - `yggdrasil` was upgraded to a new major release with breaking changes, see [upstream changelog](https://github.com/yggdrasil-network/yggdrasil-go/releases/tag/v0.4.0).
 
@@ -227,6 +339,7 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 - `tt-rss` was upgraded to the commit on 2021-06-21, which has breaking changes. If you use `services.tt-rss.extraConfig` you should migrate to the `putenv`-style configuration. See [this Discourse post](https://community.tt-rss.org/t/rip-config-php-hello-classes-config-php/4337) in the tt-rss forums for more details.
 
 - The following Visual Studio Code extensions were renamed to keep the naming convention uniform.
+
   - `bbenoist.Nix` -> `bbenoist.nix`
   - `CoenraadS.bracket-pair-colorizer` -> `coenraads.bracket-pair-colorizer`
   - `golang.Go` -> `golang.go`
@@ -246,12 +359,12 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
 - The `yambar` package has been split into `yambar` and `yambar-wayland`, corresponding to the xorg and wayland backend respectively. Please switch to `yambar-wayland` if you are on wayland.
 
 - The `services.minio` module gained an additional option `consoleAddress`, that
-configures the address and port the web UI is listening, it defaults to `:9001`.
-To be able to access the web UI this port needs to be opened in the firewall.
+  configures the address and port the web UI is listening, it defaults to `:9001`.
+  To be able to access the web UI this port needs to be opened in the firewall.
 
-- The `varnish` package was upgraded from 6.3.x to 6.5.x. `varnish60` for the last LTS release is also still available.
+- The `varnish` package was upgraded from 6.3.x to 7.x. `varnish60` for the last LTS release is also still available.
 
-- The `kubernetes` package was upgraded to 1.22.  The `kubernetes.apiserver.kubeletHttps` option was removed and HTTPS is always used.
+- The `kubernetes` package was upgraded to 1.22. The `kubernetes.apiserver.kubeletHttps` option was removed and HTTPS is always used.
 
 - The attribute `linuxPackages_latest_hardened` was dropped because the hardened patches
   lag behind the upstream kernel which made version bumps harder. If you want to use
@@ -265,12 +378,57 @@ To be able to access the web UI this port needs to be opened in the firewall.
 
 - The `todoman` package was upgraded from 3.9.0 to 4.0.0. This introduces breaking changes in the [configuration file](https://todoman.readthedocs.io/en/stable/configure.html#configuration-file) format.
 
+- The `datadog-agent`, `datadog-integrations-core` and `datadog-process-agent` packages
+  were upgraded from 6.11.2 to 7.30.2, git-2018-09-18 to 7.30.1 and 6.11.1 to 7.30.2,
+  respectively. As a result `services.datadog-agent` has had breaking changes to the
+  configuration file. For details, see the [upstream changelog](https://github.com/DataDog/datadog-agent/blob/main/CHANGELOG.rst).
+
+- `opencv2` no longer includes the non-free libraries by default, and consequently `pfstools` no longer includes OpenCV support by default.  Both packages now support an `enableUnfree` option to re-enable this functionality.
+- `services.xserver.displayManager.defaultSession = "plasma5"` does not work anymore, instead use either `"plasma"` for the Plasma X11 session or `"plasmawayland"` for the Plasma Wayland sesison.
+
+- `boot.kernelParams` now only accepts one command line parameter per string. This change is aimed to reduce common mistakes like "param = 12", which would be parsed as 3 parameters.
+
+- `nix.daemonNiceLevel` and `nix.daemonIONiceLevel` have been removed in favour of the new options [`nix.daemonCPUSchedPolicy`](options.html#opt-nix.daemonCPUSchedPolicy), [`nix.daemonIOSchedClass`](options.html#opt-nix.daemonIOSchedClass) and [`nix.daemonIOSchedPriority`](options.html#opt-nix.daemonIOSchedPriority). Please refer to the options documentation and the `sched(7)` and `ioprio_set(2)` man pages for guidance on how to use them.
+
+- The `coursier` package's binary was renamed from `coursier` to `cs`. Completions which haven't worked for a while should now work with the renamed binary. To keep using `coursier`, you can create a shell alias.
+
+- The `services.mosquitto` module has been rewritten to support multiple listeners and per-listener configuration.
+  Module configurations from previous releases will no longer work and must be updated.
+
+- The `fluidsynth_1` attribute has been removed, as this legacy version is no longer needed in nixpkgs. The actively maintained 2.x series is available as `fluidsynth` unchanged.
+
+- Nextcloud 20 (`pkgs.nextcloud20`) has been dropped because it was EOLed by upstream in 2021-10.
+
+- The `virtualisation.pathsInNixDB` option was renamed
+  [`virtualisation.additionalPaths`](options.html#opt-virtualisation.additionalPaths).
+
+- The `services.ddclient.password` option was removed, and replaced with `services.ddclient.passwordFile`.
+
+- The default GNAT version has been changed: The `gnat` attribute now points to `gnat11`
+  instead of `gnat9`.
+
+- `retroArchCores` has been removed. This means that using `nixpkgs.config.retroarch` to customize RetroArch cores is not supported anymore. Instead, use package overrides, for example: `retroarch.override { cores = with libretro; [ citra snes9x ]; };`. Also, `retroarchFull` derivation is available for those who want to have all RetroArch cores available.
+
 ## Other Notable Changes {#sec-release-21.11-notable-changes}
 
+
+- The linux kernel package infrastructure was moved out of `all-packages.nix`, and restructured. Linux related functions and attributes now live under the `pkgs.linuxKernel` attribute set.
+  In particular the versioned `linuxPackages_*` package sets (such as `linuxPackages_5_4`) and kernels from `pkgs` were moved there and now live under `pkgs.linuxKernel.packages.*`. The unversioned ones (such as `linuxPackages_latest`) remain untouched.
+
+- In NixOS virtual machines (QEMU), the `virtualisation` module has been updated with new options:
+    - [`forwardPorts`](options.html#opt-virtualisation.forwardPorts) to configure IPv4 port forwarding,
+    - [`sharedDirectories`](options.html#opt-virtualisation.sharedDirectories) to set up shared host directories,
+    - [`resolution`](options.html#opt-virtualisation.resolution) to set the screen resolution,
+    - [`useNixStoreImage`](options.html#opt-virtualisation.useNixStoreImage) to use a disk image for the Nix store instead of 9P.
+
+  In addition, the default [`msize`](options.html#opt-virtualisation.msize) parameter in 9P filesystems (including /nix/store and all shared directories) has been increased to 16K for improved performance.
+
 - The setting [`services.openssh.logLevel`](options.html#opt-services.openssh.logLevel) `"VERBOSE"` `"INFO"`. This brings NixOS in line with upstream and other Linux distributions, and reduces log spam on servers due to bruteforcing botnets.
 
   However, if [`services.fail2ban.enable`](options.html#opt-services.fail2ban.enable) is `true`, the `fail2ban` will override the verbosity to `"VERBOSE"`, so that `fail2ban` can observe the failed login attempts from the SSH logs.
 
+- The [`services.xserver.extraLayouts`](options.html#opt-services.xserver.extraLayouts) no longer cause additional rebuilds when a layout is added or modified.
+
 - Sway: The terminal emulator `rxvt-unicode` is no longer installed by default via `programs.sway.extraPackages`. The current default configuration uses `alacritty` (and soon `foot`) so this is only an issue when using a customized configuration and not installing `rxvt-unicode` explicitly.
 
 - `python3` now defaults to Python 3.9. Python 3.9 introduces many deprecation warnings, please look at the [What's New In Python 3.9 post](https://docs.python.org/3/whatsnew/3.9.html) for more information.
@@ -279,7 +437,11 @@ To be able to access the web UI this port needs to be opened in the firewall.
 
 - The `claws-mail` package now references the new GTK+ 3 release branch, major version 4. To use the GTK+ 2 releases, one can install the `claws-mail-gtk2` package.
 
-- The wordpress module provides a new interface which allows to use different webservers with the new option [`services.wordpress.webserver`](options.html#opt-services.wordpress.webserver).  Currently `httpd` and `nginx` are supported. The definitions of wordpress sites should now be set in [`services.wordpress.sites`](options.html#opt-services.wordpress.sites).
+- The wordpress module provides a new interface which allows to use different webservers with the new option [`services.wordpress.webserver`](options.html#opt-services.wordpress.webserver).  Currently `httpd`, `caddy` and `nginx` are supported. The definitions of wordpress sites should now be set in [`services.wordpress.sites`](options.html#opt-services.wordpress.sites).
+
+  Sites definitions that use the old interface are automatically migrated in the new option. This backward compatibility will be removed in 22.05.
+
+- The dokuwiki module provides a new interface which allows to use different webservers with the new option [`services.dokuwiki.webserver`](options.html#opt-services.dokuwiki.webserver).  Currently `caddy` and `nginx` are supported. The definitions of dokuwiki sites should now be set in [`services.dokuwiki.sites`](options.html#opt-services.dokuwiki.sites).
 
   Sites definitions that use the old interface are automatically migrated in the new option. This backward compatibility will be removed in 22.05.
 
@@ -307,8 +469,20 @@ To be able to access the web UI this port needs to be opened in the firewall.
     `myhostname`, but before `dns` should use the default priority
   - NSS modules which should come after `dns` should use mkAfter.
 
+- The [networking.wireless](options.html#opt-networking.wireless.enable) module (based on wpa_supplicant) has been heavily reworked, solving a number of issues and adding useful features:
+  - The automatic discovery of wireless interfaces at boot has been made reliable again (issues [#101963](https://github.com/NixOS/nixpkgs/issues/101963), [#23196](https://github.com/NixOS/nixpkgs/issues/23196)).
+  - WPA3 and Fast BSS Transition (802.11r) are now enabled by default for all networks.
+  - Secrets like pre-shared keys and passwords can now be handled safely, meaning without including them in a world-readable file (`wpa_supplicant.conf` under /nix/store).
+    This is achieved by storing the secrets in a secured [environmentFile](options.html#opt-networking.wireless.environmentFile) and referring to them though environment variables that are expanded inside the configuration.
+  - With multiple interfaces declared, independent wpa_supplicant daemons are started, one for each interface (the services are named `wpa_supplicant-wlan0`, `wpa_supplicant-wlan1`, etc.).
+  - The generated `wpa_supplicant.conf` file is now formatted for easier reading.
+  - A new [scanOnLowSignal](options.html#opt-networking.wireless.scanOnLowSignal) option has been added to facilitate fast roaming between access points (enabled by default).
+  - A new [networks.&lt;name&gt;.authProtocols](options.html#opt-networking.wireless.networks._name_.authProtocols) option has been added to change the authentication protocols used when connecting to a network.
+
 - The [networking.wireless.iwd](options.html#opt-networking.wireless.iwd.enable) module has a new [networking.wireless.iwd.settings](options.html#opt-networking.wireless.iwd.settings) option.
 
+- The [services.smokeping.host](options.html#opt-services.smokeping.host) option was added and defaulted to `localhost`. Before, `smokeping` listened to all interfaces by default. NixOS defaults generally aim to provide non-Internet-exposed defaults for databases and internal monitoring tools, see e.g. [#100192](https://github.com/NixOS/nixpkgs/issues/100192). Further, the systemd service for `smokeping` got reworked defaults for increased operational stability, see [PR #144127](https://github.com/NixOS/nixpkgs/pull/144127) for details.
+
 - The [services.syncoid.enable](options.html#opt-services.syncoid.enable) module now properly drops ZFS permissions after usage. Before it delegated permissions to whole pools instead of datasets and didn't clean up after execution. You can manually look this up for your pools by running `zfs allow your-pool-name` and use `zfs unallow syncoid your-pool-name` to clean this up.
 
 - Zfs: `latestCompatibleLinuxPackages` is now exported on the zfs package. One can use `boot.kernelPackages = config.boot.zfs.package.latestCompatibleLinuxPackages;` to always track the latest compatible kernel with a given version of zfs.
@@ -317,6 +491,55 @@ To be able to access the web UI this port needs to be opened in the firewall.
 
 - `lib.formats.yaml`'s `generate` will not generate JSON anymore, but instead use more of the YAML-specific syntax.
 
+- MariaDB was upgraded from 10.5.x to 10.6.x. Please read the [upstream release notes](https://mariadb.com/kb/en/changes-improvements-in-mariadb-106/) for changes and upgrade instructions.
+
+- The MariaDB C client library, also known as libmysqlclient or mariadb-connector-c, was upgraded from 3.1.x to 3.2.x. While this should hopefully not have any impact, this upgrade comes with some changes to default behavior, so you might want to review the [upstream release notes](https://mariadb.com/kb/en/changes-and-improvements-in-mariadb-connector-c-32/).
+
 - GNOME desktop environment now enables `QGnomePlatform` as the Qt platform theme, which should avoid crashes when opening file chooser dialogs in Qt apps by using XDG desktop portal. Additionally, it will make the apps fit better visually.
 
 - `rofi` has been updated from '1.6.1' to '1.7.0', one important thing is the removal of the old xresources based configuration setup. Read more [in rofi's changelog](https://github.com/davatorium/rofi/blob/cb12e6fc058f4a0f4f/Changelog#L1).
+
+- ipfs now defaults to not listening on you local network. This setting was change as server providers won't accept port scanning on their private network. If you have several ipfs instances running on a network you own, feel free to change the setting `ipfs.localDiscovery = true;`. localDiscovery enables different instances to discover each other and share data.
+
+- `lua` and `luajit` interpreters have been patched to avoid looking into /usr/lib
+  directories, thus increasing the purity of the build.
+
+- Three new options, [xdg.mime.addedAssociations](#opt-xdg.mime.addedAssociations), [xdg.mime.defaultApplications](#opt-xdg.mime.defaultApplications), and [xdg.mime.removedAssociations](#opt-xdg.mime.removedAssociations) have been added to the [xdg.mime](#opt-xdg.mime.enable) module to allow the configuration of `/etc/xdg/mimeapps.list`.
+
+- Kopia was upgraded from 0.8.x to 0.9.x. Please read the [upstream release notes](https://github.com/kopia/kopia/releases/tag/v0.9.0) for changes and upgrade instructions.
+
+- The `systemd.network` module has gained support for the FooOverUDP link type.
+
+- The `networking` module has a new `networking.fooOverUDP` option to configure Foo-over-UDP encapsulations.
+
+- `networking.sits` now supports Foo-over-UDP encapsulation.
+
+- Changing systemd `.socket` units now restarts them and stops the service that is activated by them. Additionally, services with `stopOnChange = false` don't break anymore when they are socket-activated.
+
+-  The `virtualisation.libvirtd` module has been refactored and updated with new options:
+    - `virtualisation.libvirtd.qemu*` options (e.g.: `virtualisation.libvirtd.qemuRunAsRoot`) were moved to [`virtualisation.libvirtd.qemu`](options.html#opt-virtualisation.libvirtd.qemu) submodule,
+    - software TPM1/TPM2 support (e.g.: Windows 11 guests) ([`virtualisation.libvirtd.qemu.swtpm`](options.html#opt-virtualisation.libvirtd.qemu.swtpm)),
+    - custom OVMF package (e.g.: `pkgs.OVMFFull` with HTTP, CSM and Secure Boot support) ([`virtualisation.libvirtd.qemu.ovmf.package`](options.html#opt-virtualisation.libvirtd.qemu.ovmf.package)).
+
+- The `cawbird` Twitter client now uses its own API keys to count as different application than upstream builds. This is done to evade application-level rate limiting. While existing accounts continue to work, users may want to remove and re-register their account in the client to enjoy a better user experience and benefit from this change.
+
+- A new option `services.prometheus.enableReload` has been added which can be enabled to reload the prometheus service when its config file changes instead of restarting.
+
+- The option `services.prometheus.environmentFile` has been removed since it was causing [issues](https://github.com/NixOS/nixpkgs/issues/126083) and Prometheus now has native support for secret files, i.e. `basic_auth.password_file` and `authorization.credentials_file`.
+
+- Dokuwiki now supports caddy! However
+  - the nginx option has been removed, in the new configuration, please use the `dokuwiki.webserver = "nginx"` instead.
+  - The "${hostname}" option has been deprecated, please use `dokuwiki.sites = [ "${hostname}" ]` instead
+
+- The [services.unifi](options.html#opt-services.unifi.enable) module has been reworked, solving a number of issues. This leads to several user facing changes:
+  - The `services.unifi.dataDir` option is removed and the data is now always located under `/var/lib/unifi/data`. This is done to make better use of systemd state direcotiry and thus making the service restart more reliable.
+  - The unifi logs can now be found under: `/var/log/unifi` instead of `/var/lib/unifi/logs`.
+  - The unifi run directory can now be found under: `/run/unifi` instead of `/var/lib/unifi/run`.
+
+- `security.pam.services.<name>.makeHomeDir` now uses `umask=0077` instead of `umask=0022` when creating the home directory.
+
+- Loki has had another release. Some default values have been changed for the configuration and some configuration options have been renamed. For more details, please check [the upgrade guide](https://grafana.com/docs/loki/latest/upgrading/#240).
+
+- `julia` now refers to `julia-stable` instead of `julia-lts`. In practice this means it has been upgraded from `1.0.4` to `1.5.4`.
+
+- RetroArch has been upgraded from version `1.8.5` to `1.9.13.2`. Since the previous release was quite old, if you're having issues after the upgrade, please delete your `$XDG_CONFIG_HOME/retroarch/retroarch.cfg` file.
diff --git a/nixos/lib/build-vms.nix b/nixos/lib/build-vms.nix
index f0a58628c68a6..05d9ce89dbdc3 100644
--- a/nixos/lib/build-vms.nix
+++ b/nixos/lib/build-vms.nix
@@ -4,15 +4,14 @@
 , # Ignored
   config ? null
 , # Nixpkgs, for qemu, lib and more
-  pkgs
+  pkgs, lib
 , # !!! See comment about args in lib/modules.nix
   specialArgs ? {}
 , # NixOS configuration to add to the VMs
   extraConfigurations ? []
 }:
 
-with pkgs.lib;
-with import ../lib/qemu-flags.nix { inherit pkgs; };
+with lib;
 
 rec {
 
@@ -69,9 +68,8 @@ rec {
                       prefixLength = 24;
                   } ];
                 });
-            in
-            { key = "ip-address";
-              config =
+
+              networkConfig =
                 { networking.hostName = mkDefault m.fst;
 
                   networking.interfaces = listToAttrs interfaces;
@@ -93,10 +91,19 @@ rec {
                          "${config.networking.hostName}\n"));
 
                   virtualisation.qemu.options =
-                    forEach interfacesNumbered
-                      ({ fst, snd }: qemuNICFlags snd fst m.snd);
+                    let qemu-common = import ../lib/qemu-common.nix { inherit lib pkgs; };
+                    in flip concatMap interfacesNumbered
+                      ({ fst, snd }: qemu-common.qemuNICFlags snd fst m.snd);
                 };
-            }
+
+              in
+                { key = "ip-address";
+                  config = networkConfig // {
+                    # Expose the networkConfig items for tests like nixops
+                    # that need to recreate the network config.
+                    system.build.networkConfig = networkConfig;
+                  };
+                }
           )
           (getAttr m.fst nodes)
         ] );
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index 15429a7160c5a..69e0a2afdba3d 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -61,7 +61,7 @@ in rec {
     args = extraArgs;
     specialArgs =
       { modulesPath = builtins.toString ../modules; } // specialArgs;
-  }) config options _module;
+  }) config options _module type;
 
   # These are the extra arguments passed to every module.  In
   # particular, Nixpkgs is passed through the "pkgs" argument.
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 55643facea03d..0a4a71fadc422 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -44,11 +44,14 @@
   #   most likely fails as GRUB will probably refuse to install.
   partitionTableType ? "legacy"
 
+, # Whether to invoke `switch-to-configuration boot` during image creation
+  installBootLoader ? true
+
 , # The root file system type.
   fsType ? "ext4"
 
 , # Filesystem label
-  label ? "nixos"
+  label ? if onlyNixStore then "nix-store" else "nixos"
 
 , # The initial NixOS configuration file to be copied to
   # /etc/nixos/configuration.nix.
@@ -57,10 +60,24 @@
 , # Shell code executed after the VM has finished.
   postVM ? ""
 
+, # Copy the contents of the Nix store to the root of the image and
+  # skip further setup. Incompatible with `contents`,
+  # `installBootLoader` and `configFile`.
+  onlyNixStore ? false
+
 , name ? "nixos-disk-image"
 
 , # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
   format ? "raw"
+
+, # Whether a nix channel based on the current source tree should be
+  # made available inside the image. Useful for interactive use of nix
+  # utils, but changes the hash of the image when the sources are
+  # updated.
+  copyChannel ? true
+
+, # Additional store paths to copy to the image's store.
+  additionalPaths ? []
 }:
 
 assert partitionTableType == "legacy" || partitionTableType == "legacy+gpt" || partitionTableType == "efi" || partitionTableType == "hybrid" || partitionTableType == "none";
@@ -71,6 +88,7 @@ assert lib.all
          (attrs: ((attrs.user  or null) == null)
               == ((attrs.group or null) == null))
          contents;
+assert onlyNixStore -> contents == [] && configFile == null && !installBootLoader;
 
 with lib;
 
@@ -163,7 +181,14 @@ let format' = format; in let
   users   = map (x: x.user  or "''") contents;
   groups  = map (x: x.group or "''") contents;
 
-  closureInfo = pkgs.closureInfo { rootPaths = [ config.system.build.toplevel channelSources ]; };
+  basePaths = [ config.system.build.toplevel ]
+    ++ lib.optional copyChannel channelSources;
+
+  additionalPaths' = subtractLists basePaths additionalPaths;
+
+  closureInfo = pkgs.closureInfo {
+    rootPaths = basePaths ++ additionalPaths';
+  };
 
   blockSize = toString (4 * 1024); # ext4fs block size (not block device sector size)
 
@@ -251,7 +276,13 @@ let format' = format; in let
     chmod 755 "$TMPDIR"
     echo "running nixos-install..."
     nixos-install --root $root --no-bootloader --no-root-passwd \
-      --system ${config.system.build.toplevel} --channel ${channelSources} --substituters ""
+      --system ${config.system.build.toplevel} \
+      ${if copyChannel then "--channel ${channelSources}" else "--no-channel-copy"} \
+      --substituters ""
+
+    ${optionalString (additionalPaths' != []) ''
+      nix copy --to $root --no-check-sigs ${concatStringsSep " " additionalPaths'}
+    ''}
 
     diskImage=nixos.raw
 
@@ -320,25 +351,29 @@ let format' = format; in let
     ''}
 
     echo "copying staging root to image..."
-    cptofs -p ${optionalString (partitionTableType != "none") "-P ${rootPartition}"} -t ${fsType} -i $diskImage $root/* / ||
+    cptofs -p ${optionalString (partitionTableType != "none") "-P ${rootPartition}"} \
+           -t ${fsType} \
+           -i $diskImage \
+           $root${optionalString onlyNixStore builtins.storeDir}/* / ||
       (echo >&2 "ERROR: cptofs failed. diskSize might be too small for closure."; exit 1)
   '';
-in pkgs.vmTools.runInLinuxVM (
-  pkgs.runCommand name
-    { preVM = prepareImage;
+
+  moveOrConvertImage = ''
+    ${if format == "raw" then ''
+      mv $diskImage $out/${filename}
+    '' else ''
+      ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
+    ''}
+    diskImage=$out/${filename}
+  '';
+
+  buildImage = pkgs.vmTools.runInLinuxVM (
+    pkgs.runCommand name {
+      preVM = prepareImage;
       buildInputs = with pkgs; [ util-linux e2fsprogs dosfstools ];
-      postVM = ''
-        ${if format == "raw" then ''
-          mv $diskImage $out/${filename}
-        '' else ''
-          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
-        ''}
-        diskImage=$out/${filename}
-        ${postVM}
-      '';
+      postVM = moveOrConvertImage + postVM;
       memSize = 1024;
-    }
-    ''
+    } ''
       export PATH=${binPath}:$PATH
 
       rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"}
@@ -368,11 +403,13 @@ in pkgs.vmTools.runInLinuxVM (
         cp ${configFile} /mnt/etc/nixos/configuration.nix
       ''}
 
-      # Set up core system link, GRUB, etc.
-      NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
+      ${lib.optionalString installBootLoader ''
+        # Set up core system link, GRUB, etc.
+        NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
 
-      # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
-      rm -f $mountPoint/etc/machine-id
+        # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
+        rm -f $mountPoint/etc/machine-id
+      ''}
 
       # Set the ownerships of the contents. The modes are set in preVM.
       # No globbing on targets, so no need to set -f
@@ -398,4 +435,9 @@ in pkgs.vmTools.runInLinuxVM (
         tune2fs -T now -c 0 -i 0 $rootDisk
       ''}
     ''
-)
+  );
+in
+  if onlyNixStore then
+    pkgs.runCommand name {}
+      (prepareImage + moveOrConvertImage + postVM)
+  else buildImage
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 14015ab64abb7..e058e70f3888e 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -83,10 +83,13 @@ let
   optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options);
 
   # Customly sort option list for the man page.
+  # Always ensure that the sort order matches sortXML.py!
   optionsList = lib.sort optionLess optionsListDesc;
 
   # Convert the list of options into an XML file.
-  optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsList);
+  # This file is *not* sorted sorted to save on eval time, since the docbook XML
+  # and the manpage depend on it and thus we evaluate this on every system rebuild.
+  optionsXML = builtins.toFile "options.xml" (builtins.toXML optionsListDesc);
 
   optionsNix = builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o ["name" "visible" "internal"]; }) optionsList);
 
@@ -185,9 +188,10 @@ in {
       exit 1
     fi
 
+    ${pkgs.python3Minimal}/bin/python ${./sortXML.py} $optionsXML sorted.xml
     ${pkgs.libxslt.bin}/bin/xsltproc \
       --stringparam revision '${revision}' \
-      -o intermediate.xml ${./options-to-docbook.xsl} $optionsXML
+      -o intermediate.xml ${./options-to-docbook.xsl} sorted.xml
     ${pkgs.libxslt.bin}/bin/xsltproc \
       -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml
   '';
diff --git a/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixos/lib/make-options-doc/options-to-docbook.xsl
index 18d19fddaca24..da4cd164bf206 100644
--- a/nixos/lib/make-options-doc/options-to-docbook.xsl
+++ b/nixos/lib/make-options-doc/options-to-docbook.xsl
@@ -54,7 +54,7 @@
                 <para>
                   <emphasis>Default:</emphasis>
                   <xsl:text> </xsl:text>
-                  <xsl:apply-templates select="attr[@name = 'default']" mode="top" />
+                  <xsl:apply-templates select="attr[@name = 'default']/*" mode="top" />
                 </para>
               </xsl:if>
 
@@ -62,14 +62,7 @@
                 <para>
                   <emphasis>Example:</emphasis>
                   <xsl:text> </xsl:text>
-                  <xsl:choose>
-                    <xsl:when test="attr[@name = 'example']/attrs[attr[@name = '_type' and string[@value = 'literalExample']]]">
-                      <programlisting><xsl:value-of select="attr[@name = 'example']/attrs/attr[@name = 'text']/string/@value" /></programlisting>
-                    </xsl:when>
-                    <xsl:otherwise>
-                      <xsl:apply-templates select="attr[@name = 'example']" mode="top" />
-                    </xsl:otherwise>
-                  </xsl:choose>
+                  <xsl:apply-templates select="attr[@name = 'example']/*" mode="top" />
                 </para>
               </xsl:if>
 
@@ -107,20 +100,37 @@
   </xsl:template>
 
 
-  <xsl:template match="*" mode="top">
+  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExpression']]]" mode = "top">
     <xsl:choose>
-      <xsl:when test="string[contains(@value, '&#010;')]">
-<programlisting>
-<xsl:text>''
-</xsl:text><xsl:value-of select='str:replace(string/@value, "${", "&apos;&apos;${")' /><xsl:text>''</xsl:text></programlisting>
+      <xsl:when test="contains(attr[@name = 'text']/string/@value, '&#010;')">
+        <programlisting><xsl:value-of select="attr[@name = 'text']/string/@value" /></programlisting>
       </xsl:when>
       <xsl:otherwise>
-        <literal><xsl:apply-templates /></literal>
+        <literal><xsl:value-of select="attr[@name = 'text']/string/@value" /></literal>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:template>
 
 
+  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalDocBook']]]" mode = "top">
+    <xsl:value-of disable-output-escaping="yes" select="attr[@name = 'text']/string/@value" />
+  </xsl:template>
+
+
+  <xsl:template match="string[contains(@value, '&#010;')]" mode="top">
+    <programlisting>
+      <xsl:text>''&#010;</xsl:text>
+      <xsl:value-of select='str:replace(str:replace(@value, "&apos;&apos;", "&apos;&apos;&apos;"), "${", "&apos;&apos;${")' />
+      <xsl:text>''</xsl:text>
+    </programlisting>
+  </xsl:template>
+
+
+  <xsl:template match="*" mode="top">
+    <literal><xsl:apply-templates select="." /></literal>
+  </xsl:template>
+
+
   <xsl:template match="null">
     <xsl:text>null</xsl:text>
   </xsl:template>
@@ -129,10 +139,10 @@
   <xsl:template match="string">
     <xsl:choose>
       <xsl:when test="(contains(@value, '&quot;') or contains(@value, '\')) and not(contains(@value, '&#010;'))">
-        <xsl:text>''</xsl:text><xsl:value-of select='str:replace(@value, "${", "&apos;&apos;${")' /><xsl:text>''</xsl:text>
+        <xsl:text>''</xsl:text><xsl:value-of select='str:replace(str:replace(@value, "&apos;&apos;", "&apos;&apos;&apos;"), "${", "&apos;&apos;${")' /><xsl:text>''</xsl:text>
       </xsl:when>
       <xsl:otherwise>
-        <xsl:text>"</xsl:text><xsl:value-of select="str:replace(str:replace(str:replace(str:replace(@value, '\', '\\'), '&quot;', '\&quot;'), '&#010;', '\n'), '$', '\$')" /><xsl:text>"</xsl:text>
+        <xsl:text>"</xsl:text><xsl:value-of select="str:replace(str:replace(str:replace(str:replace(@value, '\', '\\'), '&quot;', '\&quot;'), '&#010;', '\n'), '${', '\${')" /><xsl:text>"</xsl:text>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:template>
@@ -163,7 +173,7 @@
   </xsl:template>
 
 
-  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExample']]]">
+  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExpression']]]">
     <xsl:value-of select="attr[@name = 'text']/string/@value" />
   </xsl:template>
 
diff --git a/nixos/lib/make-options-doc/sortXML.py b/nixos/lib/make-options-doc/sortXML.py
new file mode 100644
index 0000000000000..717820788c944
--- /dev/null
+++ b/nixos/lib/make-options-doc/sortXML.py
@@ -0,0 +1,28 @@
+import xml.etree.ElementTree as ET
+import sys
+
+tree = ET.parse(sys.argv[1])
+# the xml tree is of the form
+# <expr><list> {all options, each an attrs} </list></expr>
+options = list(tree.getroot().find('list'))
+
+def sortKey(opt):
+    def order(s):
+        if s.startswith("enable"):
+            return 0
+        if s.startswith("package"):
+            return 1
+        return 2
+
+    return [
+        (order(p.attrib['value']), p.attrib['value'])
+        for p in opt.findall('attr[@name="loc"]/list/string')
+    ]
+
+# always ensure that the sort order matches the order used in the nix expression!
+options.sort(key=sortKey)
+
+doc = ET.Element("expr")
+newOptions = ET.SubElement(doc, "list")
+newOptions.extend(options)
+ET.ElementTree(doc).write(sys.argv[2], encoding='utf-8')
diff --git a/nixos/lib/make-zfs-image.nix b/nixos/lib/make-zfs-image.nix
index 40648ca24d4d3..a84732aa11712 100644
--- a/nixos/lib/make-zfs-image.nix
+++ b/nixos/lib/make-zfs-image.nix
@@ -241,7 +241,7 @@ let
     pkgs.vmTools.override {
       rootModules =
         [ "zfs" "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ] ++
-          (pkgs.lib.optional (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) "rtc_cmos");
+          (pkgs.lib.optional pkgs.stdenv.hostPlatform.isx86 "rtc_cmos");
       kernel = modulesTree;
     }
   ).runInLinuxVM (
diff --git a/nixos/lib/qemu-flags.nix b/nixos/lib/qemu-common.nix
index f786745ba3247..1a1f7531feb0b 100644
--- a/nixos/lib/qemu-flags.nix
+++ b/nixos/lib/qemu-common.nix
@@ -1,12 +1,12 @@
-# QEMU flags shared between various Nix expressions.
-{ pkgs }:
+# QEMU-related utilities shared between various Nix expressions.
+{ lib, pkgs }:
 
 let
   zeroPad = n:
-    pkgs.lib.optionalString (n < 16) "0" +
+    lib.optionalString (n < 16) "0" +
       (if n > 255
        then throw "Can't have more than 255 nets or nodes!"
-       else pkgs.lib.toHexString n);
+       else lib.toHexString n);
 in
 
 rec {
@@ -14,10 +14,10 @@ rec {
 
   qemuNICFlags = nic: net: 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}"
+      ''-netdev vde,id=vlan${toString nic},sock="$QEMU_VDE_SOCKET_${toString net}"''
     ];
 
-  qemuSerialDevice = if pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64 then "ttyS0"
+  qemuSerialDevice = if pkgs.stdenv.hostPlatform.isx86 then "ttyS0"
         else if (with pkgs.stdenv.hostPlatform; isAarch32 || isAarch64 || isPower) then "ttyAMA0"
         else throw "Unknown QEMU serial device for system '${pkgs.stdenv.hostPlatform.system}'";
 
diff --git a/nixos/lib/test-driver/test-driver.py b/nixos/lib/test-driver/test-driver.py
index f8502188bde8d..643446f313e3a 100755
--- a/nixos/lib/test-driver/test-driver.py
+++ b/nixos/lib/test-driver/test-driver.py
@@ -4,15 +4,14 @@ from queue import Queue, Empty
 from typing import Tuple, Any, Callable, Dict, Iterator, Optional, List, Iterable
 from xml.sax.saxutils import XMLGenerator
 from colorama import Style
+from pathlib import Path
 import queue
 import io
-import _thread
+import threading
 import argparse
-import atexit
 import base64
 import codecs
 import os
-import pathlib
 import ptpython.repl
 import pty
 import re
@@ -21,7 +20,6 @@ import shutil
 import socket
 import subprocess
 import sys
-import telnetlib
 import tempfile
 import time
 import unicodedata
@@ -89,55 +87,6 @@ CHAR_TO_KEY = {
     ")": "shift-0x0B",
 }
 
-global log, machines, test_script
-
-
-def eprint(*args: object, **kwargs: Any) -> None:
-    print(*args, file=sys.stderr, **kwargs)
-
-
-def make_command(args: list) -> str:
-    return " ".join(map(shlex.quote, (map(str, args))))
-
-
-def create_vlan(vlan_nr: str) -> Tuple[str, str, "subprocess.Popen[bytes]", Any]:
-    log.log("starting VDE switch for network {}".format(vlan_nr))
-    vde_socket = tempfile.mkdtemp(
-        prefix="nixos-test-vde-", suffix="-vde{}.ctl".format(vlan_nr)
-    )
-    pty_master, pty_slave = pty.openpty()
-    vde_process = subprocess.Popen(
-        ["vde_switch", "-s", vde_socket, "--dirmode", "0700"],
-        stdin=pty_slave,
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-        shell=False,
-    )
-    fd = os.fdopen(pty_master, "w")
-    fd.write("version\n")
-    # TODO: perl version checks if this can be read from
-    # an if not, dies. we could hang here forever. Fix it.
-    assert vde_process.stdout is not None
-    vde_process.stdout.readline()
-    if not os.path.exists(os.path.join(vde_socket, "ctl")):
-        raise Exception("cannot start vde_switch")
-
-    return (vlan_nr, vde_socket, vde_process, fd)
-
-
-def retry(fn: Callable, timeout: int = 900) -> None:
-    """Call the given function repeatedly, with 1 second intervals,
-    until it returns True or a timeout is reached.
-    """
-
-    for _ in range(timeout):
-        if fn(False):
-            return
-        time.sleep(1)
-
-    if not fn(True):
-        raise Exception(f"action timed out after {timeout} seconds")
-
 
 class Logger:
     def __init__(self) -> None:
@@ -151,6 +100,10 @@ class Logger:
 
         self._print_serial_logs = True
 
+    @staticmethod
+    def _eprint(*args: object, **kwargs: Any) -> None:
+        print(*args, file=sys.stderr, **kwargs)
+
     def close(self) -> None:
         self.xml.endElement("logfile")
         self.xml.endDocument()
@@ -169,15 +122,27 @@ class Logger:
         self.xml.characters(message)
         self.xml.endElement("line")
 
+    def info(self, *args, **kwargs) -> None:  # type: ignore
+        self.log(*args, **kwargs)
+
+    def warning(self, *args, **kwargs) -> None:  # type: ignore
+        self.log(*args, **kwargs)
+
+    def error(self, *args, **kwargs) -> None:  # type: ignore
+        self.log(*args, **kwargs)
+        sys.exit(1)
+
     def log(self, message: str, attributes: Dict[str, str] = {}) -> None:
-        eprint(self.maybe_prefix(message, attributes))
+        self._eprint(self.maybe_prefix(message, attributes))
         self.drain_log_queue()
         self.log_line(message, attributes)
 
     def log_serial(self, message: str, machine: str) -> None:
         self.enqueue({"msg": message, "machine": machine, "type": "serial"})
         if self._print_serial_logs:
-            eprint(Style.DIM + "{} # {}".format(machine, message) + Style.RESET_ALL)
+            self._eprint(
+                Style.DIM + "{} # {}".format(machine, message) + Style.RESET_ALL
+            )
 
     def enqueue(self, item: Dict[str, str]) -> None:
         self.queue.put(item)
@@ -194,7 +159,7 @@ class Logger:
 
     @contextmanager
     def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
-        eprint(self.maybe_prefix(message, attributes))
+        self._eprint(self.maybe_prefix(message, attributes))
 
         self.xml.startElement("nest", attrs={})
         self.xml.startElement("head", attributes)
@@ -211,6 +176,27 @@ class Logger:
         self.xml.endElement("nest")
 
 
+rootlog = Logger()
+
+
+def make_command(args: list) -> str:
+    return " ".join(map(shlex.quote, (map(str, args))))
+
+
+def retry(fn: Callable, timeout: int = 900) -> None:
+    """Call the given function repeatedly, with 1 second intervals,
+    until it returns True or a timeout is reached.
+    """
+
+    for _ in range(timeout):
+        if fn(False):
+            return
+        time.sleep(1)
+
+    if not fn(True):
+        raise Exception(f"action timed out after {timeout} seconds")
+
+
 def _perform_ocr_on_screenshot(
     screenshot_path: str, model_ids: Iterable[int]
 ) -> List[str]:
@@ -242,113 +228,266 @@ def _perform_ocr_on_screenshot(
     return model_results
 
 
-class Machine:
-    def __repr__(self) -> str:
-        return f"<Machine '{self.name}'>"
-
-    def __init__(self, args: Dict[str, Any]) -> None:
-        if "name" in args:
-            self.name = args["name"]
-        else:
-            self.name = "machine"
-            cmd = args.get("startCommand", None)
-            if cmd:
-                match = re.search("run-(.+)-vm$", cmd)
-                if match:
-                    self.name = match.group(1)
-        self.logger = args["log"]
-        self.script = args.get("startCommand", self.create_startcommand(args))
-
-        tmp_dir = os.environ.get("TMPDIR", tempfile.gettempdir())
-
-        def create_dir(name: str) -> str:
-            path = os.path.join(tmp_dir, name)
-            os.makedirs(path, mode=0o700, exist_ok=True)
-            return path
+class StartCommand:
+    """The Base Start Command knows how to append the necesary
+    runtime qemu options as determined by a particular test driver
+    run. Any such start command is expected to happily receive and
+    append additional qemu args.
+    """
 
-        self.state_dir = os.path.join(tmp_dir, f"vm-state-{self.name}")
-        if not args.get("keepVmState", False):
-            self.cleanup_statedir()
-        os.makedirs(self.state_dir, mode=0o700, exist_ok=True)
-        self.shared_dir = create_dir("shared-xchg")
+    _cmd: str
 
-        self.booted = False
-        self.connected = False
-        self.pid: Optional[int] = None
-        self.socket = None
-        self.monitor: Optional[socket.socket] = None
-        self.allow_reboot = args.get("allowReboot", False)
+    def cmd(
+        self,
+        monitor_socket_path: Path,
+        shell_socket_path: Path,
+        allow_reboot: bool = False,  # TODO: unused, legacy?
+    ) -> str:
+        display_opts = ""
+        display_available = any(x in os.environ for x in ["DISPLAY", "WAYLAND_DISPLAY"])
+        if not display_available:
+            display_opts += " -nographic"
+
+        # qemu options
+        qemu_opts = ""
+        qemu_opts += (
+            ""
+            if allow_reboot
+            else " -no-reboot"
+            " -device virtio-serial"
+            " -device virtconsole,chardev=shell"
+            " -device virtio-rng-pci"
+            " -serial stdio"
+        )
+        # TODO: qemu script already catpures this env variable, legacy?
+        qemu_opts += " " + os.environ.get("QEMU_OPTS", "")
+
+        return (
+            f"{self._cmd}"
+            f" -monitor unix:{monitor_socket_path}"
+            f" -chardev socket,id=shell,path={shell_socket_path}"
+            f"{qemu_opts}"
+            f"{display_opts}"
+        )
 
     @staticmethod
-    def create_startcommand(args: Dict[str, str]) -> str:
-        net_backend = "-netdev user,id=net0"
-        net_frontend = "-device virtio-net-pci,netdev=net0"
+    def build_environment(
+        state_dir: Path,
+        shared_dir: Path,
+    ) -> dict:
+        # We make a copy to not update the current environment
+        env = dict(os.environ)
+        env.update(
+            {
+                "TMPDIR": str(state_dir),
+                "SHARED_DIR": str(shared_dir),
+                "USE_TMPDIR": "1",
+            }
+        )
+        return env
+
+    def run(
+        self,
+        state_dir: Path,
+        shared_dir: Path,
+        monitor_socket_path: Path,
+        shell_socket_path: Path,
+    ) -> subprocess.Popen:
+        return subprocess.Popen(
+            self.cmd(monitor_socket_path, shell_socket_path),
+            stdin=subprocess.DEVNULL,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT,
+            shell=True,
+            cwd=state_dir,
+            env=self.build_environment(state_dir, shared_dir),
+        )
 
-        if "netBackendArgs" in args:
-            net_backend += "," + args["netBackendArgs"]
 
-        if "netFrontendArgs" in args:
-            net_frontend += "," + args["netFrontendArgs"]
+class NixStartScript(StartCommand):
+    """A start script from nixos/modules/virtualiation/qemu-vm.nix
+    that also satisfies the requirement of the BaseStartCommand.
+    These Nix commands have the particular charactersitic that the
+    machine name can be extracted out of them via a regex match.
+    (Admittedly a _very_ implicit contract, evtl. TODO fix)
+    """
 
-        start_command = (
-            args.get("qemuBinary", "qemu-kvm")
-            + " -m 384 "
-            + net_backend
-            + " "
-            + net_frontend
-            + " $QEMU_OPTS "
-        )
+    def __init__(self, script: str):
+        self._cmd = script
 
-        if "hda" in args:
-            hda_path = os.path.abspath(args["hda"])
-            if args.get("hdaInterface", "") == "scsi":
-                start_command += (
-                    "-drive id=hda,file="
-                    + hda_path
-                    + ",werror=report,if=none "
-                    + "-device scsi-hd,drive=hda "
+    @property
+    def machine_name(self) -> str:
+        match = re.search("run-(.+)-vm$", self._cmd)
+        name = "machine"
+        if match:
+            name = match.group(1)
+        return name
+
+
+class LegacyStartCommand(StartCommand):
+    """Used in some places to create an ad-hoc machine instead of
+    using nix test instrumentation + module system for that purpose.
+    Legacy.
+    """
+
+    def __init__(
+        self,
+        netBackendArgs: Optional[str] = None,
+        netFrontendArgs: Optional[str] = None,
+        hda: Optional[Tuple[Path, str]] = None,
+        cdrom: Optional[str] = None,
+        usb: Optional[str] = None,
+        bios: Optional[str] = None,
+        qemuFlags: Optional[str] = None,
+    ):
+        self._cmd = "qemu-kvm -m 384"
+
+        # networking
+        net_backend = "-netdev user,id=net0"
+        net_frontend = "-device virtio-net-pci,netdev=net0"
+        if netBackendArgs is not None:
+            net_backend += "," + netBackendArgs
+        if netFrontendArgs is not None:
+            net_frontend += "," + netFrontendArgs
+        self._cmd += f" {net_backend} {net_frontend}"
+
+        # hda
+        hda_cmd = ""
+        if hda is not None:
+            hda_path = hda[0].resolve()
+            hda_interface = hda[1]
+            if hda_interface == "scsi":
+                hda_cmd += (
+                    f" -drive id=hda,file={hda_path},werror=report,if=none"
+                    " -device scsi-hd,drive=hda"
                 )
             else:
-                start_command += (
-                    "-drive file="
-                    + hda_path
-                    + ",if="
-                    + args["hdaInterface"]
-                    + ",werror=report "
-                )
+                hda_cmd += f" -drive file={hda_path},if={hda_interface},werror=report"
+        self._cmd += hda_cmd
 
-        if "cdrom" in args:
-            start_command += "-cdrom " + args["cdrom"] + " "
+        # cdrom
+        if cdrom is not None:
+            self._cmd += f" -cdrom {cdrom}"
 
-        if "usb" in args:
+        # usb
+        usb_cmd = ""
+        if usb is not None:
             # https://github.com/qemu/qemu/blob/master/docs/usb2.txt
-            start_command += (
-                "-device usb-ehci -drive "
-                + "id=usbdisk,file="
-                + args["usb"]
-                + ",if=none,readonly "
-                + "-device usb-storage,drive=usbdisk "
+            usb_cmd += (
+                " -device usb-ehci"
+                f" -drive id=usbdisk,file={usb},if=none,readonly"
+                " -device usb-storage,drive=usbdisk "
             )
-        if "bios" in args:
-            start_command += "-bios " + args["bios"] + " "
+        self._cmd += usb_cmd
+
+        # bios
+        if bios is not None:
+            self._cmd += f" -bios {bios}"
 
-        start_command += args.get("qemuFlags", "")
+        # qemu flags
+        if qemuFlags is not None:
+            self._cmd += f" {qemuFlags}"
+
+
+class Machine:
+    """A handle to the machine with this name, that also knows how to manage
+    the machine lifecycle with the help of a start script / command."""
+
+    name: str
+    tmp_dir: Path
+    shared_dir: Path
+    state_dir: Path
+    monitor_path: Path
+    shell_path: Path
+
+    start_command: StartCommand
+    keep_vm_state: bool
+    allow_reboot: bool
+
+    process: Optional[subprocess.Popen]
+    pid: Optional[int]
+    monitor: Optional[socket.socket]
+    shell: Optional[socket.socket]
+    serial_thread: Optional[threading.Thread]
+
+    booted: bool
+    connected: bool
+    # Store last serial console lines for use
+    # of wait_for_console_text
+    last_lines: Queue = Queue()
+
+    def __repr__(self) -> str:
+        return f"<Machine '{self.name}'>"
+
+    def __init__(
+        self,
+        tmp_dir: Path,
+        start_command: StartCommand,
+        name: str = "machine",
+        keep_vm_state: bool = False,
+        allow_reboot: bool = False,
+    ) -> None:
+        self.tmp_dir = tmp_dir
+        self.keep_vm_state = keep_vm_state
+        self.allow_reboot = allow_reboot
+        self.name = name
+        self.start_command = start_command
+
+        # set up directories
+        self.shared_dir = self.tmp_dir / "shared-xchg"
+        self.shared_dir.mkdir(mode=0o700, exist_ok=True)
+
+        self.state_dir = self.tmp_dir / f"vm-state-{self.name}"
+        self.monitor_path = self.state_dir / "monitor"
+        self.shell_path = self.state_dir / "shell"
+        if (not self.keep_vm_state) and self.state_dir.exists():
+            self.cleanup_statedir()
+        self.state_dir.mkdir(mode=0o700, exist_ok=True)
 
-        return start_command
+        self.process = None
+        self.pid = None
+        self.monitor = None
+        self.shell = None
+        self.serial_thread = None
+
+        self.booted = False
+        self.connected = False
+
+    @staticmethod
+    def create_startcommand(args: Dict[str, str]) -> StartCommand:
+        rootlog.warning(
+            "Using legacy create_startcommand(),"
+            "please use proper nix test vm instrumentation, instead"
+            "to generate the appropriate nixos test vm qemu startup script"
+        )
+        hda = None
+        if args.get("hda"):
+            hda_arg: str = args.get("hda", "")
+            hda_arg_path: Path = Path(hda_arg)
+            hda = (hda_arg_path, args.get("hdaInterface", ""))
+        return LegacyStartCommand(
+            netBackendArgs=args.get("netBackendArgs"),
+            netFrontendArgs=args.get("netFrontendArgs"),
+            hda=hda,
+            cdrom=args.get("cdrom"),
+            usb=args.get("usb"),
+            bios=args.get("bios"),
+            qemuFlags=args.get("qemuFlags"),
+        )
 
     def is_up(self) -> bool:
         return self.booted and self.connected
 
     def log(self, msg: str) -> None:
-        self.logger.log(msg, {"machine": self.name})
+        rootlog.log(msg, {"machine": self.name})
 
     def log_serial(self, msg: str) -> None:
-        self.logger.log_serial(msg, self.name)
+        rootlog.log_serial(msg, self.name)
 
     def nested(self, msg: str, attrs: Dict[str, str] = {}) -> _GeneratorContextManager:
         my_attrs = {"machine": self.name}
         my_attrs.update(attrs)
-        return self.logger.nested(msg, my_attrs)
+        return rootlog.nested(msg, my_attrs)
 
     def wait_for_monitor_prompt(self) -> str:
         assert self.monitor is not None
@@ -442,23 +581,40 @@ class Machine:
                     + "'{}' but it is in state ‘{}’".format(require_state, state)
                 )
 
-    def execute(self, command: str) -> Tuple[int, str]:
+    def _next_newline_closed_block_from_shell(self) -> str:
+        assert self.shell
+        output_buffer = []
+        while True:
+            # This receives up to 4096 bytes from the socket
+            chunk = self.shell.recv(4096)
+            if not chunk:
+                # Probably a broken pipe, return the output we have
+                break
+
+            decoded = chunk.decode()
+            output_buffer += [decoded]
+            if decoded[-1] == "\n":
+                break
+        return "".join(output_buffer)
+
+    def execute(self, command: str, check_return: bool = True) -> Tuple[int, str]:
         self.connect()
 
-        out_command = "( set -euo pipefail; {} ); echo '|!=EOF' $?\n".format(command)
+        out_command = f"( set -euo pipefail; {command} ) | (base64 --wrap 0; echo)\n"
+        assert self.shell
         self.shell.send(out_command.encode())
 
-        output = ""
-        status_code_pattern = re.compile(r"(.*)\|\!=EOF\s+(\d+)")
+        # Get the output
+        output = base64.b64decode(self._next_newline_closed_block_from_shell())
 
-        while True:
-            chunk = self.shell.recv(4096).decode(errors="ignore")
-            match = status_code_pattern.match(chunk)
-            if match:
-                output += match[1]
-                status_code = int(match[2])
-                return (status_code, output)
-            output += chunk
+        if not check_return:
+            return (-1, output.decode())
+
+        # Get the return code
+        self.shell.send("echo ${PIPESTATUS[0]}\n".encode())
+        rc = int(self._next_newline_closed_block_from_shell().strip())
+
+        return (rc, output.decode())
 
     def shell_interact(self) -> None:
         """Allows you to interact with the guest shell
@@ -466,6 +622,8 @@ class Machine:
         Should only be used during test development, not in the production test."""
         self.connect()
         self.log("Terminal is ready (there is no prompt):")
+
+        assert self.shell
         subprocess.run(
             ["socat", "READLINE", f"FD:{self.shell.fileno()}"],
             pass_fds=[self.shell.fileno()],
@@ -534,6 +692,7 @@ class Machine:
 
         with self.nested("waiting for the VM to power off"):
             sys.stdout.flush()
+            assert self.process
             self.process.wait()
 
             self.pid = None
@@ -611,6 +770,8 @@ class Machine:
         with self.nested("waiting for the VM to finish booting"):
             self.start()
 
+            assert self.shell
+
             tic = time.time()
             self.shell.recv(1024)
             # TODO: Timeout
@@ -653,12 +814,12 @@ class Machine:
         """Copy a file from the host into the guest via the `shared_dir` shared
         among all the VMs (using a temporary directory).
         """
-        host_src = pathlib.Path(source)
-        vm_target = pathlib.Path(target)
+        host_src = Path(source)
+        vm_target = Path(target)
         with tempfile.TemporaryDirectory(dir=self.shared_dir) as shared_td:
-            shared_temp = pathlib.Path(shared_td)
+            shared_temp = Path(shared_td)
             host_intermediate = shared_temp / host_src.name
-            vm_shared_temp = pathlib.Path("/tmp/shared") / shared_temp.name
+            vm_shared_temp = Path("/tmp/shared") / shared_temp.name
             vm_intermediate = vm_shared_temp / host_src.name
 
             self.succeed(make_command(["mkdir", "-p", vm_shared_temp]))
@@ -675,11 +836,11 @@ class Machine:
         all the VMs (using a temporary directory).
         """
         # Compute the source, target, and intermediate shared file names
-        out_dir = pathlib.Path(os.environ.get("out", os.getcwd()))
-        vm_src = pathlib.Path(source)
+        out_dir = Path(os.environ.get("out", os.getcwd()))
+        vm_src = Path(source)
         with tempfile.TemporaryDirectory(dir=self.shared_dir) as shared_td:
-            shared_temp = pathlib.Path(shared_td)
-            vm_shared_temp = pathlib.Path("/tmp/shared") / shared_temp.name
+            shared_temp = Path(shared_td)
+            vm_shared_temp = Path("/tmp/shared") / shared_temp.name
             vm_intermediate = vm_shared_temp / vm_src.name
             intermediate = shared_temp / vm_src.name
             # Copy the file to the shared directory inside VM
@@ -750,72 +911,43 @@ class Machine:
 
         self.log("starting vm")
 
-        def create_socket(path: str) -> socket.socket:
-            if os.path.exists(path):
-                os.unlink(path)
+        def clear(path: Path) -> Path:
+            if path.exists():
+                path.unlink()
+            return path
+
+        def create_socket(path: Path) -> socket.socket:
             s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
-            s.bind(path)
+            s.bind(str(path))
             s.listen(1)
             return s
 
-        monitor_path = os.path.join(self.state_dir, "monitor")
-        self.monitor_socket = create_socket(monitor_path)
-
-        shell_path = os.path.join(self.state_dir, "shell")
-        self.shell_socket = create_socket(shell_path)
-
-        display_available = any(x in os.environ for x in ["DISPLAY", "WAYLAND_DISPLAY"])
-        qemu_options = (
-            " ".join(
-                [
-                    "" if self.allow_reboot else "-no-reboot",
-                    "-monitor unix:{}".format(monitor_path),
-                    "-chardev socket,id=shell,path={}".format(shell_path),
-                    "-device virtio-serial",
-                    "-device virtconsole,chardev=shell",
-                    "-device virtio-rng-pci",
-                    "-serial stdio" if display_available else "-nographic",
-                ]
-            )
-            + " "
-            + os.environ.get("QEMU_OPTS", "")
-        )
-
-        environment = dict(os.environ)
-        environment.update(
-            {
-                "TMPDIR": self.state_dir,
-                "SHARED_DIR": self.shared_dir,
-                "USE_TMPDIR": "1",
-                "QEMU_OPTS": qemu_options,
-            }
+        monitor_socket = create_socket(clear(self.monitor_path))
+        shell_socket = create_socket(clear(self.shell_path))
+        self.process = self.start_command.run(
+            self.state_dir,
+            self.shared_dir,
+            self.monitor_path,
+            self.shell_path,
         )
-
-        self.process = subprocess.Popen(
-            self.script,
-            stdin=subprocess.DEVNULL,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.STDOUT,
-            shell=True,
-            cwd=self.state_dir,
-            env=environment,
-        )
-        self.monitor, _ = self.monitor_socket.accept()
-        self.shell, _ = self.shell_socket.accept()
+        self.monitor, _ = monitor_socket.accept()
+        self.shell, _ = 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
+            assert self.process
+            assert self.process.stdout
             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)
                 self.log_serial(line)
 
-        _thread.start_new_thread(process_serial_output, ())
+        self.serial_thread = threading.Thread(target=process_serial_output)
+        self.serial_thread.start()
 
         self.wait_for_monitor_prompt()
 
@@ -825,15 +957,15 @@ class Machine:
         self.log("QEMU running (pid {})".format(self.pid))
 
     def cleanup_statedir(self) -> None:
-        if os.path.isdir(self.state_dir):
-            shutil.rmtree(self.state_dir)
-            self.logger.log(f"deleting VM state directory {self.state_dir}")
-            self.logger.log("if you want to keep the VM state, pass --keep-vm-state")
+        shutil.rmtree(self.state_dir)
+        rootlog.log(f"deleting VM state directory {self.state_dir}")
+        rootlog.log("if you want to keep the VM state, pass --keep-vm-state")
 
     def shutdown(self) -> None:
         if not self.booted:
             return
 
+        assert self.shell
         self.shell.send("poweroff\n".encode())
         self.wait_for_shutdown()
 
@@ -908,41 +1040,222 @@ class Machine:
         """Make the machine reachable."""
         self.send_monitor_command("set_link virtio-net-pci.1 on")
 
+    def release(self) -> None:
+        if self.pid is None:
+            return
+        rootlog.info(f"kill machine (pid {self.pid})")
+        assert self.process
+        assert self.shell
+        assert self.monitor
+        assert self.serial_thread
 
-def create_machine(args: Dict[str, Any]) -> Machine:
-    args["log"] = log
-    return Machine(args)
+        self.process.terminate()
+        self.shell.close()
+        self.monitor.close()
+        self.serial_thread.join()
 
 
-def start_all() -> None:
-    with log.nested("starting all VMs"):
-        for machine in machines:
-            machine.start()
+class VLan:
+    """This class handles a VLAN that the run-vm scripts identify via its
+    number handles. The network's lifetime equals the object's lifetime.
+    """
+
+    nr: int
+    socket_dir: Path
+
+    process: subprocess.Popen
+    pid: int
+    fd: io.TextIOBase
+
+    def __repr__(self) -> str:
+        return f"<Vlan Nr. {self.nr}>"
 
+    def __init__(self, nr: int, tmp_dir: Path):
+        self.nr = nr
+        self.socket_dir = tmp_dir / f"vde{self.nr}.ctl"
 
-def join_all() -> None:
-    with log.nested("waiting for all VMs to finish"):
-        for machine in machines:
-            machine.wait_for_shutdown()
+        # TODO: don't side-effect environment here
+        os.environ[f"QEMU_VDE_SOCKET_{self.nr}"] = str(self.socket_dir)
 
+        rootlog.info("start vlan")
+        pty_master, pty_slave = pty.openpty()
 
-def run_tests(interactive: bool = False) -> None:
-    if interactive:
-        ptpython.repl.embed(test_symbols(), {})
-    else:
-        test_script()
+        self.process = subprocess.Popen(
+            ["vde_switch", "-s", self.socket_dir, "--dirmode", "0700"],
+            stdin=pty_slave,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            shell=False,
+        )
+        self.pid = self.process.pid
+        self.fd = os.fdopen(pty_master, "w")
+        self.fd.write("version\n")
+
+        # TODO: perl version checks if this can be read from
+        # an if not, dies. we could hang here forever. Fix it.
+        assert self.process.stdout is not None
+        self.process.stdout.readline()
+        if not (self.socket_dir / "ctl").exists():
+            rootlog.error("cannot start vde_switch")
+
+        rootlog.info(f"running vlan (pid {self.pid})")
+
+    def __del__(self) -> None:
+        rootlog.info(f"kill vlan (pid {self.pid})")
+        self.fd.close()
+        self.process.terminate()
+
+
+class Driver:
+    """A handle to the driver that sets up the environment
+    and runs the tests"""
+
+    tests: str
+    vlans: List[VLan]
+    machines: List[Machine]
+
+    def __init__(
+        self,
+        start_scripts: List[str],
+        vlans: List[int],
+        tests: str,
+        keep_vm_state: bool = False,
+    ):
+        self.tests = tests
+
+        tmp_dir = Path(os.environ.get("TMPDIR", tempfile.gettempdir()))
+        tmp_dir.mkdir(mode=0o700, exist_ok=True)
+
+        with rootlog.nested("start all VLans"):
+            self.vlans = [VLan(nr, tmp_dir) for nr in vlans]
+
+        def cmd(scripts: List[str]) -> Iterator[NixStartScript]:
+            for s in scripts:
+                yield NixStartScript(s)
+
+        self.machines = [
+            Machine(
+                start_command=cmd,
+                keep_vm_state=keep_vm_state,
+                name=cmd.machine_name,
+                tmp_dir=tmp_dir,
+            )
+            for cmd in cmd(start_scripts)
+        ]
+
+    def __enter__(self) -> "Driver":
+        return self
+
+    def __exit__(self, *_: Any) -> None:
+        with rootlog.nested("cleanup"):
+            for machine in self.machines:
+                machine.release()
+
+    def subtest(self, name: str) -> Iterator[None]:
+        """Group logs under a given test name"""
+        with rootlog.nested(name):
+            try:
+                yield
+                return True
+            except Exception as e:
+                rootlog.error(f'Test "{name}" failed with error: "{e}"')
+                raise e
+
+    def test_symbols(self) -> Dict[str, Any]:
+        @contextmanager
+        def subtest(name: str) -> Iterator[None]:
+            return self.subtest(name)
+
+        general_symbols = dict(
+            start_all=self.start_all,
+            test_script=self.test_script,
+            machines=self.machines,
+            vlans=self.vlans,
+            driver=self,
+            log=rootlog,
+            os=os,
+            create_machine=self.create_machine,
+            subtest=subtest,
+            run_tests=self.run_tests,
+            join_all=self.join_all,
+            retry=retry,
+            serial_stdout_off=self.serial_stdout_off,
+            serial_stdout_on=self.serial_stdout_on,
+            Machine=Machine,  # for typing
+        )
+        machine_symbols = {m.name: m for m in self.machines}
+        # If there's exactly one machine, make it available under the name
+        # "machine", even if it's not called that.
+        if len(self.machines) == 1:
+            (machine_symbols["machine"],) = self.machines
+        vlan_symbols = {
+            f"vlan{v.nr}": self.vlans[idx] for idx, v in enumerate(self.vlans)
+        }
+        print(
+            "additionally exposed symbols:\n    "
+            + ", ".join(map(lambda m: m.name, self.machines))
+            + ",\n    "
+            + ", ".join(map(lambda v: f"vlan{v.nr}", self.vlans))
+            + ",\n    "
+            + ", ".join(list(general_symbols.keys()))
+        )
+        return {**general_symbols, **machine_symbols, **vlan_symbols}
+
+    def test_script(self) -> None:
+        """Run the test script"""
+        with rootlog.nested("run the VM test script"):
+            symbols = self.test_symbols()  # call eagerly
+            exec(self.tests, symbols, None)
+
+    def run_tests(self) -> None:
+        """Run the test script (for non-interactive test runs)"""
+        self.test_script()
         # TODO: Collect coverage data
-        for machine in machines:
+        for machine in self.machines:
             if machine.is_up():
                 machine.execute("sync")
 
+    def start_all(self) -> None:
+        """Start all machines"""
+        with rootlog.nested("start all VMs"):
+            for machine in self.machines:
+                machine.start()
+
+    def join_all(self) -> None:
+        """Wait for all machines to shut down"""
+        with rootlog.nested("wait for all VMs to finish"):
+            for machine in self.machines:
+                machine.wait_for_shutdown()
+
+    def create_machine(self, args: Dict[str, Any]) -> Machine:
+        rootlog.warning(
+            "Using legacy create_machine(), please instantiate the"
+            "Machine class directly, instead"
+        )
+        tmp_dir = Path(os.environ.get("TMPDIR", tempfile.gettempdir()))
+        tmp_dir.mkdir(mode=0o700, exist_ok=True)
 
-def serial_stdout_on() -> None:
-    log._print_serial_logs = True
+        if args.get("startCommand"):
+            start_command: str = args.get("startCommand", "")
+            cmd = NixStartScript(start_command)
+            name = args.get("name", cmd.machine_name)
+        else:
+            cmd = Machine.create_startcommand(args)  # type: ignore
+            name = args.get("name", "machine")
+
+        return Machine(
+            tmp_dir=tmp_dir,
+            start_command=cmd,
+            name=name,
+            keep_vm_state=args.get("keep_vm_state", False),
+            allow_reboot=args.get("allow_reboot", False),
+        )
 
+    def serial_stdout_on(self) -> None:
+        rootlog._print_serial_logs = True
 
-def serial_stdout_off() -> None:
-    log._print_serial_logs = False
+    def serial_stdout_off(self) -> None:
+        rootlog._print_serial_logs = False
 
 
 class EnvDefault(argparse.Action):
@@ -970,52 +1283,6 @@ class EnvDefault(argparse.Action):
         setattr(namespace, self.dest, values)
 
 
-@contextmanager
-def subtest(name: str) -> Iterator[None]:
-    with log.nested(name):
-        try:
-            yield
-            return True
-        except Exception as e:
-            log.log(f'Test "{name}" failed with error: "{e}"')
-            raise e
-
-    return False
-
-
-def _test_symbols() -> Dict[str, Any]:
-    general_symbols = dict(
-        start_all=start_all,
-        test_script=globals().get("test_script"),  # same
-        machines=globals().get("machines"),  # without being initialized
-        log=globals().get("log"),  # extracting those symbol keys
-        os=os,
-        create_machine=create_machine,
-        subtest=subtest,
-        run_tests=run_tests,
-        join_all=join_all,
-        retry=retry,
-        serial_stdout_off=serial_stdout_off,
-        serial_stdout_on=serial_stdout_on,
-        Machine=Machine,  # for typing
-    )
-    return general_symbols
-
-
-def test_symbols() -> Dict[str, Any]:
-
-    general_symbols = _test_symbols()
-
-    machine_symbols = {m.name: machines[idx] for idx, m in enumerate(machines)}
-    print(
-        "additionally exposed symbols:\n    "
-        + ", ".join(map(lambda m: m.name, machines))
-        + ",\n    "
-        + ", ".join(list(general_symbols.keys()))
-    )
-    return {**general_symbols, **machine_symbols}
-
-
 if __name__ == "__main__":
     arg_parser = argparse.ArgumentParser(prog="nixos-test-driver")
     arg_parser.add_argument(
@@ -1051,48 +1318,21 @@ if __name__ == "__main__":
         action=EnvDefault,
         envvar="testScript",
         help="the test script to run",
-        type=pathlib.Path,
+        type=Path,
     )
 
     args = arg_parser.parse_args()
-    testscript = pathlib.Path(args.testscript).read_text()
-
-    global log, machines, test_script
-
-    log = Logger()
-
-    vde_sockets = [create_vlan(v) for v in args.vlans]
-    for nr, vde_socket, _, _ in vde_sockets:
-        os.environ["QEMU_VDE_SOCKET_{}".format(nr)] = vde_socket
-
-    machines = [
-        create_machine({"startCommand": s, "keepVmState": args.keep_vm_state})
-        for s in args.start_scripts
-    ]
-    machine_eval = [
-        "{0} = machines[{1}]".format(m.name, idx) for idx, m in enumerate(machines)
-    ]
-    exec("\n".join(machine_eval))
-
-    @atexit.register
-    def clean_up() -> None:
-        with log.nested("cleaning up"):
-            for machine in machines:
-                if machine.pid is None:
-                    continue
-                log.log("killing {} (pid {})".format(machine.name, machine.pid))
-                machine.process.kill()
-            for _, _, process, _ in vde_sockets:
-                process.terminate()
-        log.close()
-
-    def test_script() -> None:
-        with log.nested("running the VM test script"):
-            symbols = test_symbols()  # call eagerly
-            exec(testscript, symbols, None)
-
-    interactive = args.interactive or (not bool(testscript))
-    tic = time.time()
-    run_tests(interactive)
-    toc = time.time()
-    print("test script finished in {:.2f}s".format(toc - tic))
+
+    if not args.keep_vm_state:
+        rootlog.info("Machine state will be reset. To keep it, pass --keep-vm-state")
+
+    with Driver(
+        args.start_scripts, args.vlans, args.testscript.read_text(), args.keep_vm_state
+    ) as driver:
+        if args.interactive:
+            ptpython.repl.embed(driver.test_symbols(), {})
+        else:
+            tic = time.time()
+            driver.run_tests()
+            toc = time.time()
+            rootlog.info(f"test script finished in {(toc-tic):.2f}s")
diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix
index 43b4f9b159b2f..4306d102b2d64 100644
--- a/nixos/lib/testing-python.nix
+++ b/nixos/lib/testing-python.nix
@@ -43,7 +43,8 @@ rec {
         from pydoc import importfile
         with open('driver-symbols', 'w') as fp:
           t = importfile('${testDriverScript}')
-          test_symbols = t._test_symbols()
+          d = t.Driver([],[],"")
+          test_symbols = d.test_symbols()
           fp.write(','.join(test_symbols.keys()))
         EOF
       '';
@@ -133,7 +134,9 @@ rec {
       vlans = map (m: m.config.virtualisation.vlans) (lib.attrValues nodes);
       vms = map (m: m.config.system.build.vm) (lib.attrValues nodes);
 
-      nodeHostNames = map (c: c.config.system.name) (lib.attrValues nodes);
+      nodeHostNames = let
+        nodesList = map (c: c.config.system.name) (lib.attrValues nodes);
+      in nodesList ++ lib.optional (lib.length nodesList == 1) "machine";
 
       # TODO: This is an implementation error and needs fixing
       # the testing famework cannot legitimately restrict hostnames further
@@ -188,14 +191,6 @@ rec {
           --set startScripts "''${vmStartScripts[*]}" \
           --set testScript "$out/test-script" \
           --set vlans '${toString vlans}'
-
-        ${lib.optionalString (testScript == "") ''
-          ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms
-          wrapProgram $out/bin/nixos-run-vms \
-            --set startScripts "''${vmStartScripts[*]}" \
-            --set testScript "${pkgs.writeText "start-all" "start_all(); join_all();"}" \
-            --set vlans '${toString vlans}'
-        ''}
       '');
 
   # Make a full-blown test
@@ -216,11 +211,41 @@ rec {
     let
       nodes = qemu_pkg:
         let
+          testScript' =
+            # Call the test script with the computed nodes.
+            if lib.isFunction testScript
+            then testScript { nodes = nodes qemu_pkg; }
+            else testScript;
+
           build-vms = import ./build-vms.nix {
-            inherit system pkgs minimal specialArgs;
+            inherit system lib pkgs minimal specialArgs;
             extraConfigurations = extraConfigurations ++ [(
+              { config, ... }:
               {
                 virtualisation.qemu.package = qemu_pkg;
+
+                # Make sure all derivations referenced by the test
+                # script are available on the nodes. When the store is
+                # accessed through 9p, this isn't important, since
+                # everything in the store is available to the guest,
+                # but when building a root image it is, as all paths
+                # that should be available to the guest has to be
+                # copied to the image.
+                virtualisation.additionalPaths =
+                  lib.optional
+                    # A testScript may evaluate nodes, which has caused
+                    # infinite recursions. The demand cycle involves:
+                    #   testScript -->
+                    #   nodes -->
+                    #   toplevel -->
+                    #   additionalPaths -->
+                    #   hasContext testScript' -->
+                    #   testScript (ad infinitum)
+                    # If we don't need to build an image, we can break this
+                    # cycle by short-circuiting when useNixStoreImage is false.
+                    (config.virtualisation.useNixStoreImage && builtins.hasContext testScript')
+                    (pkgs.writeStringReferencesToFile testScript');
+
                 # Ensure we do not use aliases. Ideally this is only set
                 # when the test framework is used by Nixpkgs NixOS tests.
                 nixpkgs.config.allowAliases = false;
@@ -257,105 +282,17 @@ rec {
         inherit test driver driverInteractive nodes;
       };
 
-  runInMachine =
-    { drv
-    , machine
-    , preBuild ? ""
-    , postBuild ? ""
-    , qemu_pkg ? pkgs.qemu_test
-    , ... # ???
-    }:
-    let
-      build-vms = import ./build-vms.nix {
-        inherit system pkgs minimal specialArgs extraConfigurations;
-      };
-
-      vm = build-vms.buildVM { }
-        [
-          machine
-          {
-            key = "run-in-machine";
-            networking.hostName = "client";
-            nix.readOnlyStore = false;
-            virtualisation.writableStore = false;
-          }
-        ];
-
-      buildrunner = writeText "vm-build" ''
-        source $1
-
-        ${coreutils}/bin/mkdir -p $TMPDIR
-        cd $TMPDIR
-
-        exec $origBuilder $origArgs
-      '';
-
-      testScript = ''
-        start_all()
-        client.wait_for_unit("multi-user.target")
-        ${preBuild}
-        client.succeed("env -i ${bash}/bin/bash ${buildrunner} /tmp/xchg/saved-env >&2")
-        ${postBuild}
-        client.succeed("sync") # flush all data before pulling the plug
-      '';
-
-      testDriver = pythonTestDriver { inherit qemu_pkg; };
-
-      vmRunCommand = writeText "vm-run" ''
-        xchg=vm-state-client/xchg
-        ${coreutils}/bin/mkdir $out
-        ${coreutils}/bin/mkdir -p $xchg
-
-        for i in $passAsFile; do
-          i2=''${i}Path
-          _basename=$(${coreutils}/bin/basename ''${!i2})
-          ${coreutils}/bin/cp ''${!i2} $xchg/$_basename
-          eval $i2=/tmp/xchg/$_basename
-          ${coreutils}/bin/ls -la $xchg
-        done
-
-        unset i i2 _basename
-        export | ${gnugrep}/bin/grep -v '^xchg=' > $xchg/saved-env
-        unset xchg
-
-        export tests='${testScript}'
-        ${testDriver}/bin/nixos-test-driver --keep-vm-state ${vm.config.system.build.vm}/bin/run-*-vm
-      ''; # */
-
-    in
-    lib.overrideDerivation drv (attrs: {
-      requiredSystemFeatures = [ "kvm" ];
-      builder = "${bash}/bin/sh";
-      args = [ "-e" vmRunCommand ];
-      origArgs = attrs.args;
-      origBuilder = attrs.builder;
-    });
-
+  abortForFunction = functionName: abort ''The ${functionName} function was
+    removed because it is not an essential part of the NixOS testing
+    infrastructure. It had no usage in NixOS or Nixpkgs and it had no designated
+    maintainer. You are free to reintroduce it by documenting it in the manual
+    and adding yourself as maintainer. It was removed in
+    https://github.com/NixOS/nixpkgs/pull/137013
+  '';
 
-  runInMachineWithX = { require ? [ ], ... } @ args:
-    let
-      client =
-        { ... }:
-        {
-          inherit require;
-          imports = [
-            ../tests/common/auto.nix
-          ];
-          virtualisation.memorySize = 1024;
-          services.xserver.enable = true;
-          test-support.displayManager.auto.enable = true;
-          services.xserver.displayManager.defaultSession = "none+icewm";
-          services.xserver.windowManager.icewm.enable = true;
-        };
-    in
-    runInMachine ({
-      machine = client;
-      preBuild =
-        ''
-          client.wait_for_x()
-        '';
-    } // args);
+  runInMachine = abortForFunction "runInMachine";
 
+  runInMachineWithX = abortForFunction "runInMachineWithX";
 
   simpleTest = as: (makeTest as).test;
 
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index 7fe812424f85b..f1fa9f07a9742 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -10,7 +10,7 @@ rec {
   # 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" ];
+  pathsNeededForBoot = [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/var/lib/nixos" "/etc" "/usr" ];
   fsNeededForBoot = fs: fs.neededForBoot || elem fs.mountPoint pathsNeededForBoot;
 
   # Check whenever `b` depends on `a` as a fileSystem
diff --git a/nixos/maintainers/scripts/azure-new/examples/basic/image.nix b/nixos/maintainers/scripts/azure-new/examples/basic/image.nix
index ad62dcd14a0ba..310eba3621a64 100644
--- a/nixos/maintainers/scripts/azure-new/examples/basic/image.nix
+++ b/nixos/maintainers/scripts/azure-new/examples/basic/image.nix
@@ -1,6 +1,6 @@
 let
   pkgs = (import ../../../../../../default.nix {});
-  machine = import "${pkgs.path}/nixos/lib/eval-config.nix" {
+  machine = import (pkgs.path + "/nixos/lib/eval-config.nix") {
     system = "x86_64-linux";
     modules = [
       ({config, ...}: { imports = [ ./system.nix ]; })
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index 6942b58f236e5..6358ec68f7cf6 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.amazonImage;
+  amiBootMode = if config.ec2.efi then "uefi" else "legacy-bios";
 
 in {
 
@@ -27,7 +28,7 @@ in {
     };
 
     contents = mkOption {
-      example = literalExample ''
+      example = literalExpression ''
         [ { source = pkgs.memtest86 + "/memtest.bin";
             target = "boot/memtest.bin";
           }
@@ -104,12 +105,14 @@ in {
        ${pkgs.jq}/bin/jq -n \
          --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
          --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-         --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
-         --arg boot_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg boot_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg boot_mode "${amiBootMode}" \
          --arg root "$rootDisk" \
          --arg boot "$bootDisk" \
         '{}
           | .label = $system_label
+          | .boot_mode = $boot_mode
           | .system = $system
           | .disks.boot.logical_bytes = $boot_logical_bytes
           | .disks.boot.file = $boot
@@ -145,9 +148,11 @@ in {
          --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
          --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
          --arg logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg boot_mode "${amiBootMode}" \
          --arg file "$diskImage" \
           '{}
           | .label = $system_label
+          | .boot_mode = $boot_mode
           | .system = $system
           | .logical_bytes = $logical_bytes
           | .file = $file
diff --git a/nixos/maintainers/scripts/ec2/create-amis.sh b/nixos/maintainers/scripts/ec2/create-amis.sh
index 691d7fcfcba44..797fe03e20952 100755
--- a/nixos/maintainers/scripts/ec2/create-amis.sh
+++ b/nixos/maintainers/scripts/ec2/create-amis.sh
@@ -1,6 +1,9 @@
 #!/usr/bin/env nix-shell
 #!nix-shell -p awscli -p jq -p qemu -i bash
 # shellcheck shell=bash
+#
+# Future Deprecation?
+# This entire thing should probably be replaced with a generic terraform config
 
 # Uploads and registers NixOS images built from the
 # <nixos/release.nix> amazonImage attribute. Images are uploaded and
@@ -15,18 +18,22 @@
 # set -x
 set -euo pipefail
 
+var () { true; }
+
 # configuration
-state_dir=$HOME/amis/ec2-images
-home_region=eu-west-1
-bucket=nixos-amis
-service_role_name=vmimport
+var ${state_dir:=$HOME/amis/ec2-images}
+var ${home_region:=eu-west-1}
+var ${bucket:=nixos-amis}
+var ${service_role_name:=vmimport}
 
-regions=(eu-west-1 eu-west-2 eu-west-3 eu-central-1 eu-north-1
+var ${regions:=eu-west-1 eu-west-2 eu-west-3 eu-central-1 eu-north-1
          us-east-1 us-east-2 us-west-1 us-west-2
          ca-central-1
          ap-southeast-1 ap-southeast-2 ap-northeast-1 ap-northeast-2
          ap-south-1 ap-east-1
-         sa-east-1)
+         sa-east-1}
+
+regions=($regions)
 
 log() {
     echo "$@" >&2
@@ -60,10 +67,21 @@ read_image_info() {
 
 # We handle a single image per invocation, store all attributes in
 # globals for convenience.
-image_label=$(read_image_info .label)
+zfs_disks=$(read_image_info .disks)
+is_zfs_image=
+if jq -e .boot <<< "$zfs_disks"; then
+  is_zfs_image=1
+  zfs_boot=".disks.boot"
+fi
+image_label="$(read_image_info .label)${is_zfs_image:+-ZFS}"
 image_system=$(read_image_info .system)
-image_file=$(read_image_info .file)
-image_logical_bytes=$(read_image_info .logical_bytes)
+image_files=( $(read_image_info ".disks.root.file") )
+
+image_logical_bytes=$(read_image_info "${zfs_boot:-.disks.root}.logical_bytes")
+
+if [[ -n "$is_zfs_image" ]]; then
+  image_files+=( $(read_image_info .disks.boot.file) )
+fi
 
 # Derived attributes
 
@@ -113,11 +131,11 @@ wait_for_import() {
     local state snapshot_id
     log "Waiting for import task $task_id to be completed"
     while true; do
-        read -r state progress snapshot_id < <(
+        read -r state message snapshot_id < <(
             aws ec2 describe-import-snapshot-tasks --region "$region" --import-task-ids "$task_id" | \
-                jq -r '.ImportSnapshotTasks[].SnapshotTaskDetail | "\(.Status) \(.Progress) \(.SnapshotId)"'
+                jq -r '.ImportSnapshotTasks[].SnapshotTaskDetail | "\(.Status) \(.StatusMessage) \(.SnapshotId)"'
         )
-        log " ... state=$state progress=$progress snapshot_id=$snapshot_id"
+        log " ... state=$state message=$message snapshot_id=$snapshot_id"
         case "$state" in
             active)
                 sleep 10
@@ -179,41 +197,48 @@ make_image_public() {
 upload_image() {
     local region=$1
 
-    local aws_path=${image_file#/}
-
-    local state_key="$region.$image_label.$image_system"
-    local task_id
-    task_id=$(read_state "$state_key" task_id)
-    local snapshot_id
-    snapshot_id=$(read_state "$state_key" snapshot_id)
-    local ami_id
-    ami_id=$(read_state "$state_key" ami_id)
+    for image_file in "${image_files[@]}"; do
+        local aws_path=${image_file#/}
 
-    if [ -z "$task_id" ]; then
-        log "Checking for image on S3"
-        if ! aws s3 ls --region "$region" "s3://${bucket}/${aws_path}" >&2; then
-            log "Image missing from aws, uploading"
-            aws s3 cp --region "$region" "$image_file" "s3://${bucket}/${aws_path}" >&2
+        if [[ -n "$is_zfs_image" ]]; then
+            local suffix=${image_file%.*}
+            suffix=${suffix##*.}
         fi
 
-        log "Importing image from S3 path s3://$bucket/$aws_path"
-
-        task_id=$(aws ec2 import-snapshot --role-name "$service_role_name" --disk-container "{
-          \"Description\": \"nixos-image-${image_label}-${image_system}\",
-          \"Format\": \"vhd\",
-          \"UserBucket\": {
-              \"S3Bucket\": \"$bucket\",
-              \"S3Key\": \"$aws_path\"
-          }
-        }" --region "$region" | jq -r '.ImportTaskId')
-
-        write_state "$state_key" task_id "$task_id"
-    fi
+        local state_key="$region.$image_label${suffix:+.${suffix}}.$image_system"
+        local task_id
+        task_id=$(read_state "$state_key" task_id)
+        local snapshot_id
+        snapshot_id=$(read_state "$state_key" snapshot_id)
+        local ami_id
+        ami_id=$(read_state "$state_key" ami_id)
+
+        if [ -z "$task_id" ]; then
+            log "Checking for image on S3"
+            if ! aws s3 ls --region "$region" "s3://${bucket}/${aws_path}" >&2; then
+                log "Image missing from aws, uploading"
+                aws s3 cp --region "$region" "$image_file" "s3://${bucket}/${aws_path}" >&2
+            fi
+
+            log "Importing image from S3 path s3://$bucket/$aws_path"
+
+            task_id=$(aws ec2 import-snapshot --role-name "$service_role_name" --disk-container "{
+              \"Description\": \"nixos-image-${image_label}-${image_system}\",
+              \"Format\": \"vhd\",
+              \"UserBucket\": {
+                  \"S3Bucket\": \"$bucket\",
+                  \"S3Key\": \"$aws_path\"
+              }
+            }" --region "$region" | jq -r '.ImportTaskId')
+
+            write_state "$state_key" task_id "$task_id"
+        fi
 
-    if [ -z "$snapshot_id" ]; then
-        snapshot_id=$(wait_for_import "$region" "$task_id")
-        write_state "$state_key" snapshot_id "$snapshot_id"
-    fi
+        if [ -z "$snapshot_id" ]; then
+            snapshot_id=$(wait_for_import "$region" "$task_id")
+            write_state "$state_key" snapshot_id "$snapshot_id"
+        fi
+    done
 
     if [ -z "$ami_id" ]; then
         log "Registering snapshot $snapshot_id as AMI"
@@ -222,6 +247,18 @@ upload_image() {
             "DeviceName=/dev/xvda,Ebs={SnapshotId=$snapshot_id,VolumeSize=$image_logical_gigabytes,DeleteOnTermination=true,VolumeType=gp3}"
         )
 
+        if [[ -n "$is_zfs_image" ]]; then
+            local root_snapshot_id=$(read_state "$region.$image_label.root.$image_system" snapshot_id)
+
+            local root_image_logical_bytes=$(read_image_info ".disks.root.logical_bytes")
+            local root_image_logical_gigabytes=$(((root_image_logical_bytes-1)/1024/1024/1024+1)) # Round to the next GB
+
+            block_device_mappings+=(
+                "DeviceName=/dev/xvdb,Ebs={SnapshotId=$root_snapshot_id,VolumeSize=$root_image_logical_gigabytes,DeleteOnTermination=true,VolumeType=gp3}"
+            )
+        fi
+
+
         local extra_flags=(
             --root-device-name /dev/xvda
             --sriov-net-support simple
@@ -241,6 +278,7 @@ upload_image() {
                 --region "$region" \
                 --architecture $amazon_arch \
                 --block-device-mappings "${block_device_mappings[@]}" \
+                --boot-mode $(read_image_info .boot_mode) \
                 "${extra_flags[@]}" \
                 | jq -r '.ImageId'
               )
@@ -248,7 +286,7 @@ upload_image() {
         write_state "$state_key" ami_id "$ami_id"
     fi
 
-    make_image_public "$region" "$ami_id"
+    [[ -v PRIVATE ]] || make_image_public "$region" "$ami_id"
 
     echo "$ami_id"
 }
@@ -276,7 +314,7 @@ copy_to_region() {
         write_state "$state_key" ami_id "$ami_id"
     fi
 
-    make_image_public "$region" "$ami_id"
+    [[ -v PRIVATE ]] || make_image_public "$region" "$ami_id"
 
     echo "$ami_id"
 }
diff --git a/nixos/maintainers/scripts/lxd/lxd-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
new file mode 100644
index 0000000000000..74634fd1671c1
--- /dev/null
+++ b/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
@@ -0,0 +1,102 @@
+# Edit this configuration file to define what should be installed on
+# your system.  Help is available in the configuration.nix(5) man page
+# and in the NixOS manual (accessible by running ‘nixos-help’).
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  imports =
+    [ # Include the default lxd configuration.
+      ../../../modules/virtualisation/lxc-container.nix
+      # Include the container-specific autogenerated configuration.
+      ./lxd.nix
+    ];
+
+  # networking.hostName = mkForce "nixos"; # Overwrite the hostname.
+  # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.
+
+  # Set your time zone.
+  # time.timeZone = "Europe/Amsterdam";
+
+  # The global useDHCP flag is deprecated, therefore explicitly set to false here.
+  # Per-interface useDHCP will be mandatory in the future, so this generated config
+  # replicates the default behaviour.
+  networking.useDHCP = false;
+  networking.interfaces.eth0.useDHCP = true;
+
+  # Configure network proxy if necessary
+  # networking.proxy.default = "http://user:password@proxy:port/";
+  # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
+
+  # Select internationalisation properties.
+  # i18n.defaultLocale = "en_US.UTF-8";
+  # console = {
+  #   font = "Lat2-Terminus16";
+  #   keyMap = "us";
+  # };
+
+  # Enable the X11 windowing system.
+  # services.xserver.enable = true;
+
+  # Configure keymap in X11
+  # services.xserver.layout = "us";
+  # services.xserver.xkbOptions = "eurosign:e";
+
+  # Enable CUPS to print documents.
+  # services.printing.enable = true;
+
+  # Enable sound.
+  # sound.enable = true;
+  # hardware.pulseaudio.enable = true;
+
+  # Enable touchpad support (enabled default in most desktopManager).
+  # services.xserver.libinput.enable = true;
+
+  # Define a user account. Don't forget to set a password with ‘passwd’.
+  # users.users.jane = {
+  #   isNormalUser = true;
+  #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
+  # };
+
+  # List packages installed in system profile. To search, run:
+  # $ nix search wget
+  # environment.systemPackages = with pkgs; [
+  #   vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
+  #   wget
+  #   firefox
+  # ];
+
+  # Some programs need SUID wrappers, can be configured further or are
+  # started in user sessions.
+  # programs.mtr.enable = true;
+  # programs.gnupg.agent = {
+  #   enable = true;
+  #   enableSSHSupport = true;
+  # };
+
+  # List services that you want to enable:
+
+  # Enable the OpenSSH daemon.
+  # services.openssh.enable = true;
+
+  # Open ports in the firewall.
+  # networking.firewall.allowedTCPPorts = [ ... ];
+  # networking.firewall.allowedUDPPorts = [ ... ];
+  # Or disable the firewall altogether.
+  # networking.firewall.enable = false;
+
+  # This value determines the NixOS release from which the default
+  # settings for stateful data, like file locations and database versions
+  # on your system were taken. It‘s perfectly fine and recommended to leave
+  # this value at the release version of the first install of this system.
+  # Before changing this value read the documentation for this option
+  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
+  system.stateVersion = "21.05"; # Did you read the comment?
+
+  # As this is intended as a stadalone image, undo some of the minimal profile stuff
+  documentation.enable = true;
+  documentation.nixos.enable = true;
+  environment.noXlibs = false;
+}
diff --git a/nixos/maintainers/scripts/lxd/lxd-image.nix b/nixos/maintainers/scripts/lxd/lxd-image.nix
new file mode 100644
index 0000000000000..c76b9fcc7f779
--- /dev/null
+++ b/nixos/maintainers/scripts/lxd/lxd-image.nix
@@ -0,0 +1,34 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+{
+  imports = [
+    ../../../modules/virtualisation/lxc-container.nix
+  ];
+
+  virtualisation.lxc.templates.nix = {
+    enable = true;
+    target = "/etc/nixos/lxd.nix";
+    template = ./nix.tpl;
+    when = [ "create" "copy" ];
+  };
+
+  # copy the config for nixos-rebuild
+  system.activationScripts.config = ''
+    if [ ! -e /etc/nixos/configuration.nix ]; then
+      mkdir -p /etc/nixos
+      cat ${./lxd-image-inner.nix} > /etc/nixos/configuration.nix
+      sed 's|../../../modules/virtualisation/lxc-container.nix|<nixpkgs/nixos/modules/virtualisation/lxc-container.nix>|g' -i /etc/nixos/configuration.nix
+    fi
+  '';
+
+  # Network
+  networking.useDHCP = false;
+  networking.interfaces.eth0.useDHCP = true;
+
+  # As this is intended as a stadalone image, undo some of the minimal profile stuff
+  documentation.enable = true;
+  documentation.nixos.enable = true;
+  environment.noXlibs = false;
+}
diff --git a/nixos/maintainers/scripts/lxd/nix.tpl b/nixos/maintainers/scripts/lxd/nix.tpl
new file mode 100644
index 0000000000000..307258ddc6286
--- /dev/null
+++ b/nixos/maintainers/scripts/lxd/nix.tpl
@@ -0,0 +1,9 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+# WARNING: THIS CONFIGURATION IS AUTOGENERATED AND WILL BE OVERWRITTEN AUTOMATICALLY
+
+{
+  networking.hostName = "{{ container.name }}";
+}
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index c5150305bd856..168bebd8d06a3 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -116,7 +116,11 @@ in
     { console.keyMap = with config.services.xserver;
         mkIf cfg.useXkbConfig
           (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
-            '${pkgs.ckbcomp}/bin/ckbcomp' -model '${xkbModel}' -layout '${layout}' \
+            '${pkgs.buildPackages.ckbcomp}/bin/ckbcomp' \
+              ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
+                "-I${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
+              } \
+              -model '${xkbModel}' -layout '${layout}' \
               -option '${xkbOptions}' -variant '${xkbVariant}' > "$out"
           '');
     }
diff --git a/nixos/modules/config/fonts/fontdir.nix b/nixos/modules/config/fonts/fontdir.nix
index c4bd3a077d339..db4b6c638ab4d 100644
--- a/nixos/modules/config/fonts/fontdir.nix
+++ b/nixos/modules/config/fonts/fontdir.nix
@@ -50,9 +50,8 @@ in
 
   config = mkIf cfg.enable {
 
-    # This is enough to make a symlink because the xserver
-    # module already links all /share/X11 paths.
     environment.systemPackages = [ x11Fonts ];
+    environment.pathsToLink = [ "/share/X11/fonts" ];
 
     services.xserver.filesSection = ''
       FontPath "${x11Fonts}/share/X11/fonts"
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix
index f87e61e3ef9f3..04952898cb761 100644
--- a/nixos/modules/config/fonts/fonts.nix
+++ b/nixos/modules/config/fonts/fonts.nix
@@ -61,7 +61,7 @@ in
       fonts = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ pkgs.dejavu_fonts ]";
+        example = literalExpression "[ pkgs.dejavu_fonts ]";
         description = "List of primary font paths.";
       };
 
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index 991b449d80b55..545d4a3dca61e 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -14,7 +14,7 @@ with lib;
           allLocales = any (x: x == "all") config.i18n.supportedLocales;
           locales = config.i18n.supportedLocales;
         };
-        example = literalExample "pkgs.glibcLocales";
+        example = literalExpression "pkgs.glibcLocales";
         description = ''
           Customized pkg.glibcLocales package.
 
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
index c2302451d702f..911c5b629a9a1 100644
--- a/nixos/modules/config/krb5/default.nix
+++ b/nixos/modules/config/krb5/default.nix
@@ -83,8 +83,8 @@ in {
       kerberos = mkOption {
         type = types.package;
         default = pkgs.krb5Full;
-        defaultText = "pkgs.krb5Full";
-        example = literalExample "pkgs.heimdalFull";
+        defaultText = literalExpression "pkgs.krb5Full";
+        example = literalExpression "pkgs.heimdal";
         description = ''
           The Kerberos implementation that will be present in
           <literal>environment.systemPackages</literal> after enabling this
@@ -96,7 +96,7 @@ in {
         type = with types; either attrs lines;
         default = {};
         apply = attrs: filterEmbeddedMetadata attrs;
-        example = literalExample ''
+        example = literalExpression ''
           {
             default_realm = "ATHENA.MIT.EDU";
           };
@@ -109,7 +109,7 @@ in {
       realms = mkOption {
         type = with types; either attrs lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "ATHENA.MIT.EDU" = {
               admin_server = "athena.mit.edu";
@@ -127,7 +127,7 @@ in {
       domain_realm = mkOption {
         type = with types; either attrs lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "example.com" = "EXAMPLE.COM";
             ".example.com" = "EXAMPLE.COM";
@@ -142,7 +142,7 @@ in {
       capaths = mkOption {
         type = with types; either attrs lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "ATHENA.MIT.EDU" = {
               "EXAMPLE.COM" = ".";
@@ -161,7 +161,7 @@ in {
       appdefaults = mkOption {
         type = with types; either attrs lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             pam = {
               debug = false;
@@ -182,7 +182,7 @@ in {
       plugins = mkOption {
         type = with types; either attrs lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             ccselect = {
               disable = "k5identity";
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
index fc35993b5a810..84da5643004f5 100644
--- a/nixos/modules/config/malloc.nix
+++ b/nixos/modules/config/malloc.nix
@@ -30,6 +30,15 @@ let
         vulnerabilities, while maintaining good performance.
       '';
     };
+
+    mimalloc = {
+      libPath = "${pkgs.mimalloc}/lib/libmimalloc.so";
+      description = ''
+        A compact and fast general purpose allocator, which may
+        optionally be built with mitigations against various heap
+        vulnerabilities.
+      '';
+    };
   };
 
   providerConf = providers.${cfg.provider};
@@ -91,7 +100,10 @@ in
       "abstractions/base" = ''
         r /etc/ld-nix.so.preload,
         r ${config.environment.etc."ld-nix.so.preload".source},
-        mr ${providerLibPath},
+        include "${pkgs.apparmorRulesFromClosure {
+            name = "mallocLib";
+            baseRules = ["mr $path/lib/**.so*"];
+          } [ mallocLib ] }"
       '';
     };
   };
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 8c4eec510e5d7..11307e331200b 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -21,7 +21,7 @@ in
 
     networking.hosts = lib.mkOption {
       type = types.attrsOf (types.listOf types.str);
-      example = literalExample ''
+      example = literalExpression ''
         {
           "127.0.0.1" = [ "foo.bar.baz" ];
           "192.168.0.2" = [ "fileserver.local" "nameserver.local" ];
@@ -34,8 +34,8 @@ in
 
     networking.hostFiles = lib.mkOption {
       type = types.listOf types.path;
-      defaultText = lib.literalExample "Hosts from `networking.hosts` and `networking.extraHosts`";
-      example = lib.literalExample ''[ "''${pkgs.my-blocklist-package}/share/my-blocklist/hosts" ]'';
+      defaultText = literalDocBook "Hosts from <option>networking.hosts</option> and <option>networking.extraHosts</option>";
+      example = literalExpression ''[ "''${pkgs.my-blocklist-package}/share/my-blocklist/hosts" ]'';
       description = ''
         Files that should be concatenated together to form <filename>/etc/hosts</filename>.
       '';
diff --git a/nixos/modules/config/power-management.nix b/nixos/modules/config/power-management.nix
index cc0ff732ffa56..710842e1503b2 100644
--- a/nixos/modules/config/power-management.nix
+++ b/nixos/modules/config/power-management.nix
@@ -35,7 +35,7 @@ in
       powerUpCommands = mkOption {
         type = types.lines;
         default = "";
-        example = literalExample ''
+        example = literalExpression ''
           "''${pkgs.hdparm}/sbin/hdparm -B 255 /dev/sda"
         '';
         description =
@@ -49,7 +49,7 @@ in
       powerDownCommands = mkOption {
         type = types.lines;
         default = "";
-        example = literalExample ''
+        example = literalExpression ''
           "''${pkgs.hdparm}/sbin/hdparm -B 255 /dev/sda"
         '';
         description =
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index 3f7ae109e8c2d..01555d28b73fb 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -149,8 +149,8 @@ in {
         default = if config.services.jack.jackd.enable
                   then pkgs.pulseaudioFull
                   else pkgs.pulseaudio;
-        defaultText = "pkgs.pulseaudio";
-        example = literalExample "pkgs.pulseaudioFull";
+        defaultText = literalExpression "pkgs.pulseaudio";
+        example = literalExpression "pkgs.pulseaudioFull";
         description = ''
           The PulseAudio derivation to use.  This can be used to enable
           features (such as JACK support, Bluetooth) via the
@@ -161,7 +161,7 @@ in {
       extraModules = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.pulseaudio-modules-bt ]";
+        example = literalExpression "[ pkgs.pulseaudio-modules-bt ]";
         description = ''
           Extra pulseaudio modules to use. This is intended for out-of-tree
           pulseaudio modules like extra bluetooth codecs.
@@ -184,7 +184,7 @@ in {
           type = types.attrsOf types.unspecified;
           default = {};
           description = "Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.";
-          example = literalExample ''{ realtime-scheduling = "yes"; }'';
+          example = literalExpression ''{ realtime-scheduling = "yes"; }'';
         };
       };
 
@@ -204,7 +204,7 @@ in {
           allowedIpRanges = mkOption {
             type = types.listOf types.str;
             default = [];
-            example = literalExample ''[ "127.0.0.1" "192.168.1.0/24" ]'';
+            example = literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
             description = ''
               A list of IP subnets that are allowed to stream to the server.
             '';
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 34e558d8603d4..ae3f618e273c3 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -136,10 +136,8 @@ in
 
     environment.binsh = mkOption {
       default = "${config.system.build.binsh}/bin/sh";
-      defaultText = "\${config.system.build.binsh}/bin/sh";
-      example = literalExample ''
-        "''${pkgs.dash}/bin/dash"
-      '';
+      defaultText = literalExpression ''"''${config.system.build.binsh}/bin/sh"'';
+      example = literalExpression ''"''${pkgs.dash}/bin/dash"'';
       type = types.path;
       visible = false;
       description = ''
@@ -152,7 +150,7 @@ in
 
     environment.shells = mkOption {
       default = [];
-      example = literalExample "[ pkgs.bashInteractive pkgs.zsh ]";
+      example = literalExpression "[ pkgs.bashInteractive pkgs.zsh ]";
       description = ''
         A list of permissible login shells for user accounts.
         No need to mention <literal>/bin/sh</literal>
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index ff2ae1da31bda..2b94b954cb80f 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -47,6 +47,15 @@ let
         '';
       };
 
+      allowDiscards = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to allow TRIM requests to the underlying device. This option
+          has security implications; please read the LUKS documentation before
+          activating it.
+        '';
+      };
     };
 
   };
@@ -194,7 +203,6 @@ in
     ];
 
     # Create missing swapfiles.
-    # FIXME: support changing the size of existing swapfiles.
     systemd.services =
       let
 
@@ -214,17 +222,14 @@ in
                 ${optionalString (sw.size != null) ''
                   currentSize=$(( $(stat -c "%s" "${sw.device}" 2>/dev/null || echo 0) / 1024 / 1024 ))
                   if [ "${toString sw.size}" != "$currentSize" ]; then
-                    fallocate -l ${toString sw.size}M "${sw.device}" ||
-                      dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
-                    if [ "${toString sw.size}" -lt "$currentSize" ]; then
-                      truncate --size "${toString sw.size}M" "${sw.device}"
-                    fi
+                    dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
                     chmod 0600 ${sw.device}
                     ${optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
                   fi
                 ''}
                 ${optionalString sw.randomEncryption.enable ''
-                  cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} ${optionalString (sw.discardPolicy != null) "--allow-discards"} ${sw.device} ${sw.deviceName}
+                  cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
+                    ${optionalString sw.randomEncryption.allowDiscards "--allow-discards"} ${sw.device} ${sw.deviceName}
                   mkswap ${sw.realDevice}
                 ''}
               '';
diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix
index e59c7a32c2878..db1f5284f5047 100644
--- a/nixos/modules/config/sysctl.nix
+++ b/nixos/modules/config/sysctl.nix
@@ -22,7 +22,7 @@ in
 
     boot.kernel.sysctl = mkOption {
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         { "net.ipv4.tcp_syncookies" = false; "vm.swappiness" = 60; }
       '';
       type = types.attrsOf sysctlOption;
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index 1292c3008c6f0..6ff4ec2921cf8 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -58,7 +58,7 @@ in
       systemPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.firefox pkgs.thunderbird ]";
+        example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
         description = ''
           The set of packages that appear in
           /run/current-system/sw.  These packages are
@@ -73,9 +73,9 @@ in
       defaultPackages = mkOption {
         type = types.listOf types.package;
         default = defaultPackages;
-        example = literalExample "[]";
+        example = [];
         description = ''
-          Set of default packages that aren't strictly neccessary
+          Set of default packages that aren't strictly necessary
           for a running system, entries can be removed for a more
           minimal NixOS installation.
 
diff --git a/nixos/modules/config/unix-odbc-drivers.nix b/nixos/modules/config/unix-odbc-drivers.nix
index abc12a627d6fa..055c3b2364e6e 100644
--- a/nixos/modules/config/unix-odbc-drivers.nix
+++ b/nixos/modules/config/unix-odbc-drivers.nix
@@ -19,7 +19,7 @@ in {
     environment.unixODBCDrivers = mkOption {
       type = types.listOf types.package;
       default = [];
-      example = literalExample "with pkgs.unixODBCDrivers; [ sqlite psql ]";
+      example = literalExpression "with pkgs.unixODBCDrivers; [ sqlite psql ]";
       description = ''
         Specifies Unix ODBC drivers to be registered in
         <filename>/etc/odbcinst.ini</filename>.  You may also want to
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index d88162558e665..629905e609559 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -123,7 +123,7 @@ let
       group = mkOption {
         type = types.str;
         apply = x: assert (builtins.stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
-        default = "nogroup";
+        default = "";
         description = "The user's primary group.";
       };
 
@@ -165,8 +165,8 @@ let
       shell = mkOption {
         type = types.nullOr (types.either types.shellPackage (passwdEntry types.path));
         default = pkgs.shadow;
-        defaultText = "pkgs.shadow";
-        example = literalExample "pkgs.bashInteractive";
+        defaultText = literalExpression "pkgs.shadow";
+        example = literalExpression "pkgs.bashInteractive";
         description = ''
           The path to the user's shell. Can use shell derivations,
           like <literal>pkgs.bashInteractive</literal>. Don’t
@@ -291,7 +291,7 @@ let
       packages = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.firefox pkgs.thunderbird ]";
+        example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
         description = ''
           The set of packages that should be made available to the user.
           This is in contrast to <option>environment.systemPackages</option>,
@@ -640,6 +640,16 @@ in {
               Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
             '';
           }
+          {
+            assertion = user.group != "";
+            message = ''
+              users.users.${user.name}.group is unset. This used to default to
+              nogroup, but this is unsafe. For example you can create a group
+              for this user with:
+              users.users.${user.name}.group = "${user.name}";
+              users.groups.${user.name} = {};
+            '';
+          }
         ]
     ));
 
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index 4cdb3f30994b3..9b6dd4cab5f5c 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -1,9 +1,17 @@
 { config, lib, pkgs, ... }:
 
 with lib;
+
+let
+  cfg = config.xdg.mime;
+  associationOptions = with types; attrsOf (
+    coercedTo (either (listOf str) str) (x: concatStringsSep ";" (toList x)) str
+  );
+in
+
 {
   meta = {
-    maintainers = teams.freedesktop.members;
+    maintainers = teams.freedesktop.members ++ (with maintainers; [ figsoda ]);
   };
 
   options = {
@@ -16,9 +24,63 @@ with lib;
         <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html">XDG MIME Applications specification</link>.
       '';
     };
+
+    xdg.mime.addedAssociations = mkOption {
+      type = associationOptions;
+      default = {};
+      example = {
+        "application/pdf" = "firefox.desktop";
+        "text/xml" = [ "nvim.desktop" "codium.desktop" ];
+      };
+      description = ''
+        Adds associations between mimetypes and applications. See the
+        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations">
+        specifications</link> for more information.
+      '';
+    };
+
+    xdg.mime.defaultApplications = mkOption {
+      type = associationOptions;
+      default = {};
+      example = {
+        "application/pdf" = "firefox.desktop";
+        "image/png" = [ "sxiv.desktop" "gimp.desktop" ];
+      };
+      description = ''
+        Sets the default applications for given mimetypes. See the
+        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#default">
+        specifications</link> for more information.
+      '';
+    };
+
+    xdg.mime.removedAssociations = mkOption {
+      type = associationOptions;
+      default = {};
+      example = {
+        "audio/mp3" = [ "mpv.desktop" "umpv.desktop" ];
+        "inode/directory" = "codium.desktop";
+      };
+      description = ''
+        Removes associations between mimetypes and applications. See the
+        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations">
+        specifications</link> for more information.
+      '';
+    };
   };
 
-  config = mkIf config.xdg.mime.enable {
+  config = mkIf cfg.enable {
+    environment.etc."xdg/mimeapps.list" = mkIf (
+      cfg.addedAssociations != {}
+      || cfg.defaultApplications != {}
+      || cfg.removedAssociations != {}
+    ) {
+      text = generators.toINI { } {
+        "Added Associations" = cfg.addedAssociations;
+        "Default Applications" = cfg.defaultApplications;
+        "Removed Associations" = cfg.removedAssociations;
+      };
+    };
+
     environment.pathsToLink = [ "/share/mime" ];
 
     environment.systemPackages = [
diff --git a/nixos/modules/config/xdg/portals/wlr.nix b/nixos/modules/config/xdg/portals/wlr.nix
index 55baab0026b26..aba1d8dbc00e5 100644
--- a/nixos/modules/config/xdg/portals/wlr.nix
+++ b/nixos/modules/config/xdg/portals/wlr.nix
@@ -37,7 +37,7 @@ in
       default = { };
 
       # Example taken from the manpage
-      example = literalExample ''
+      example = literalExpression ''
         {
           screencast = {
             output_name = "HDMI-A-1";
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index bdf90816740c8..ce87f9e8be8a4 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -83,7 +83,7 @@ in {
         b43Firmware_5_1_138
         b43Firmware_6_30_163_46
         b43FirmwareCutter
-      ] ++ optional (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) facetimehd-firmware;
+      ] ++ optional pkgs.stdenv.hostPlatform.isx86 facetimehd-firmware;
     })
     (mkIf cfg.wirelessRegulatoryDatabase {
       hardware.firmware = [ pkgs.wireless-regdb ];
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index 6932be1c54cad..b2bbd77c9d7fa 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -27,7 +27,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.ckb-next;
-        defaultText = "pkgs.ckb-next";
+        defaultText = literalExpression "pkgs.ckb-next";
         description = ''
           The package implementing the Corsair keyboard/mouse driver.
         '';
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
index 4aa1d6369d1bb..be67116ad507d 100644
--- a/nixos/modules/hardware/device-tree.nix
+++ b/nixos/modules/hardware/device-tree.nix
@@ -21,7 +21,7 @@ let
           each .dtb file matching "compatible" of the overlay.
         '';
         default = null;
-        example = literalExample "./dts/overlays.dts";
+        example = literalExpression "./dts/overlays.dts";
       };
 
       dtsText = mkOption {
@@ -31,7 +31,7 @@ let
           Literal DTS contents, overlay is applied to
           each .dtb file matching "compatible" of the overlay.
         '';
-        example = literalExample ''
+        example = ''
           /dts-v1/;
           /plugin/;
           / {
@@ -125,8 +125,8 @@ in
 
         kernelPackage = mkOption {
           default = config.boot.kernelPackages.kernel;
-          defaultText = "config.boot.kernelPackages.kernel";
-          example = literalExample "pkgs.linux_latest";
+          defaultText = literalExpression "config.boot.kernelPackages.kernel";
+          example = literalExpression "pkgs.linux_latest";
           type = types.path;
           description = ''
             Kernel package containing the base device-tree (.dtb) to boot. Uses
@@ -156,7 +156,7 @@ in
 
         overlays = mkOption {
           default = [];
-          example = literalExample ''
+          example = literalExpression ''
             [
               { name = "pps"; dtsFile = ./dts/pps.dts; }
               { name = "spi";
diff --git a/nixos/modules/hardware/digitalbitbox.nix b/nixos/modules/hardware/digitalbitbox.nix
index 0888cfbef2a81..097448a74f4d9 100644
--- a/nixos/modules/hardware/digitalbitbox.nix
+++ b/nixos/modules/hardware/digitalbitbox.nix
@@ -19,7 +19,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.digitalbitbox;
-      defaultText = "pkgs.digitalbitbox";
+      defaultText = literalExpression "pkgs.digitalbitbox";
       description = "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
     };
   };
diff --git a/nixos/modules/hardware/flirc.nix b/nixos/modules/hardware/flirc.nix
new file mode 100644
index 0000000000000..94ec715b9fa51
--- /dev/null
+++ b/nixos/modules/hardware/flirc.nix
@@ -0,0 +1,12 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.hardware.flirc;
+in
+{
+  options.hardware.flirc.enable = lib.mkEnableOption "software to configure a Flirc USB device";
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.flirc ];
+    services.udev.packages = [ pkgs.flirc ];
+  };
+}
diff --git a/nixos/modules/hardware/gkraken.nix b/nixos/modules/hardware/gkraken.nix
new file mode 100644
index 0000000000000..97d15369db0a0
--- /dev/null
+++ b/nixos/modules/hardware/gkraken.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.gkraken;
+in
+{
+  options.hardware.gkraken = {
+    enable = mkEnableOption "gkraken's udev rules for NZXT AIO liquid coolers";
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = with pkgs; [
+      gkraken
+    ];
+  };
+}
diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix
index a50b5d32c3580..0d8aaf734591d 100644
--- a/nixos/modules/hardware/opengl.nix
+++ b/nixos/modules/hardware/opengl.nix
@@ -89,7 +89,7 @@ in
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "with pkgs; [ vaapiIntel libvdpau-va-gl vaapiVdpau intel-ocl ]";
+        example = literalExpression "with pkgs; [ vaapiIntel libvdpau-va-gl vaapiVdpau intel-ocl ]";
         description = ''
           Additional packages to add to OpenGL drivers. This can be used
           to add OpenCL drivers, VA-API/VDPAU drivers etc.
@@ -99,7 +99,7 @@ in
       extraPackages32 = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "with pkgs.pkgsi686Linux; [ vaapiIntel libvdpau-va-gl vaapiVdpau ]";
+        example = literalExpression "with pkgs.pkgsi686Linux; [ vaapiIntel libvdpau-va-gl vaapiVdpau ]";
         description = ''
           Additional packages to add to 32-bit OpenGL drivers on
           64-bit systems. Used when <option>driSupport32Bit</option> is
diff --git a/nixos/modules/hardware/opentabletdriver.nix b/nixos/modules/hardware/opentabletdriver.nix
index 295e23e6164fa..caba934ebe773 100644
--- a/nixos/modules/hardware/opentabletdriver.nix
+++ b/nixos/modules/hardware/opentabletdriver.nix
@@ -29,7 +29,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.opentabletdriver;
-        defaultText = "pkgs.opentabletdriver";
+        defaultText = literalExpression "pkgs.opentabletdriver";
         description = ''
           OpenTabletDriver derivation to use.
         '';
diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix
index c587076dcd18e..ef07542950bac 100644
--- a/nixos/modules/hardware/printers.nix
+++ b/nixos/modules/hardware/printers.nix
@@ -72,10 +72,10 @@ in {
             };
             deviceUri = mkOption {
               type = types.str;
-              example = [
+              example = literalExpression ''
                 "ipp://printserver.local/printers/BrotherHL_Workroom"
                 "usb://HP/DESKJET%20940C?serial=CN16E6C364BH"
-              ];
+              '';
               description = ''
                 How to reach the printer.
                 <command>lpinfo -v</command> shows a list of supported device URIs and schemes.
@@ -83,8 +83,8 @@ in {
             };
             model = mkOption {
               type = types.str;
-              example = literalExample ''
-                gutenprint.''${lib.versions.majorMinor (lib.getVersion pkgs.gutenprint)}://brother-hl-5140/expert
+              example = literalExpression ''
+                "gutenprint.''${lib.versions.majorMinor (lib.getVersion pkgs.gutenprint)}://brother-hl-5140/expert"
               '';
               description = ''
                 Location of the ppd driver file for the printer.
@@ -116,19 +116,14 @@ in {
       description = "Ensure NixOS-configured CUPS printers";
       wantedBy = [ "multi-user.target" ];
       requires = [ cupsUnit ];
-      # in contrast to cups.socket, for cups.service, this is actually not enough,
-      # as the cups service reports its activation before clients can actually interact with it.
-      # Because of this, commands like `lpinfo -v` will report a bad file descriptor
-      # due to the missing UNIX socket without sufficient sleep time.
       after = [ cupsUnit ];
 
       serviceConfig = {
         Type = "oneshot";
+        RemainAfterExit = true;
       };
 
-       # sleep 10 is required to wait until cups.service is actually initialized and has created its UNIX socket file
-      script = (optionalString (!config.services.printing.startWhenNeeded) "sleep 10\n")
-        + (concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters)
+      script = concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters
         + optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter);
     };
   };
diff --git a/nixos/modules/hardware/sata.nix b/nixos/modules/hardware/sata.nix
index 541897527a8df..81592997d6e3d 100644
--- a/nixos/modules/hardware/sata.nix
+++ b/nixos/modules/hardware/sata.nix
@@ -39,7 +39,7 @@ in
     enable = mkEnableOption "SATA drive timeouts";
 
     deciSeconds = mkOption {
-      example = "70";
+      example = 70;
       type = types.int;
       description = ''
         Set SCT Error Recovery Control timeout in deciseconds for use in RAID configurations.
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index cf87ca5377dd5..5b379505608a4 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -165,11 +165,11 @@ in
     hardware.nvidia.package = lib.mkOption {
       type = lib.types.package;
       default = config.boot.kernelPackages.nvidiaPackages.stable;
-      defaultText = "config.boot.kernelPackages.nvidiaPackages.stable";
+      defaultText = literalExpression "config.boot.kernelPackages.nvidiaPackages.stable";
       description = ''
         The NVIDIA X11 derivation to use.
       '';
-      example = "config.boot.kernelPackages.nvidiaPackages.legacy_340";
+      example = literalExpression "config.boot.kernelPackages.nvidiaPackages.legacy_340";
     };
   };
 
@@ -213,7 +213,7 @@ in
       }
 
       {
-        assertion = cfg.powerManagement.enable -> offloadCfg.enable;
+        assertion = cfg.powerManagement.finegrained -> offloadCfg.enable;
         message = "Fine-grained power management requires offload to be enabled.";
       }
 
@@ -284,13 +284,17 @@ in
       source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";
     };
 
+    # 'nvidia_x11' installs it's files to /run/opengl-driver/...
+    environment.etc."egl/egl_external_platform.d".source =
+      "/run/opengl-driver/share/egl/egl_external_platform.d/";
+
     hardware.opengl.package = mkIf (!offloadCfg.enable) nvidia_x11.out;
     hardware.opengl.package32 = mkIf (!offloadCfg.enable) nvidia_x11.lib32;
     hardware.opengl.extraPackages = optional offloadCfg.enable nvidia_x11.out;
     hardware.opengl.extraPackages32 = optional offloadCfg.enable nvidia_x11.lib32;
 
     environment.systemPackages = [ nvidia_x11.bin ]
-      ++ optionals nvidiaSettings [ nvidia_x11.settings ]
+      ++ optionals cfg.nvidiaSettings [ nvidia_x11.settings ]
       ++ optionals nvidiaPersistencedEnabled [ nvidia_x11.persistenced ];
 
     systemd.packages = optional cfg.powerManagement.enable nvidia_x11.out;
diff --git a/nixos/modules/hardware/video/uvcvideo/default.nix b/nixos/modules/hardware/video/uvcvideo/default.nix
index cf6aa052abb05..338062cf69b7f 100644
--- a/nixos/modules/hardware/video/uvcvideo/default.nix
+++ b/nixos/modules/hardware/video/uvcvideo/default.nix
@@ -33,7 +33,7 @@ in
 
       packages = mkOption {
         type = types.listOf types.path;
-        example = literalExample "[ pkgs.tiscamera ]";
+        example = literalExpression "[ pkgs.tiscamera ]";
         description = ''
           List of packages containing <command>uvcvideo</command> dynamic controls
           rules. All files found in
diff --git a/nixos/modules/i18n/input-method/fcitx.nix b/nixos/modules/i18n/input-method/fcitx.nix
index 440f13b415225..57960cc365b6e 100644
--- a/nixos/modules/i18n/input-method/fcitx.nix
+++ b/nixos/modules/i18n/input-method/fcitx.nix
@@ -17,7 +17,7 @@ in
       engines = mkOption {
         type    = with types; listOf fcitxEngine;
         default = [];
-        example = literalExample "with pkgs.fcitx-engines; [ mozc hangul ]";
+        example = literalExpression "with pkgs.fcitx-engines; [ mozc hangul ]";
         description =
           let
             enginesDrv = filterAttrs (const isDerivation) pkgs.fcitx-engines;
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index eecbe32fea494..414aabbbaa730 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -12,7 +12,7 @@ in {
       addons = mkOption {
         type = with types; listOf package;
         default = [];
-        example = with pkgs; [ fcitx5-rime ];
+        example = literalExpression "with pkgs; [ fcitx5-rime ]";
         description = ''
           Enabled Fcitx5 addons.
         '';
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index 1aaa5a952bea2..92f8c64338a4c 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -36,7 +36,7 @@ in
       engines = mkOption {
         type    = with types; listOf ibusEngine;
         default = [];
-        example = literalExample "with pkgs.ibus-engines; [ mozc hangul ]";
+        example = literalExpression "with pkgs.ibus-engines; [ mozc hangul ]";
         description =
           let
             enginesDrv = filterAttrs (const isDerivation) pkgs.ibus-engines;
@@ -48,7 +48,7 @@ in
       panel = mkOption {
         type = with types; nullOr path;
         default = null;
-        example = literalExample "''${pkgs.plasma5Packages.plasma-desktop}/lib/libexec/kimpanel-ibus-panel";
+        example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/lib/libexec/kimpanel-ibus-panel"'';
         description = "Replace the IBus panel with another panel.";
       };
     };
diff --git a/nixos/modules/i18n/input-method/kime.nix b/nixos/modules/i18n/input-method/kime.nix
index 2a73cb3f46059..e462cae2437b4 100644
--- a/nixos/modules/i18n/input-method/kime.nix
+++ b/nixos/modules/i18n/input-method/kime.nix
@@ -10,7 +10,7 @@ in
       config = mkOption {
         type = yamlFormat.type;
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           {
             daemon = {
               modules = ["Xim" "Indicator"];
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 78cbf14bbaf69..30610b4f42608 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -467,7 +467,7 @@ let
       throw "Unsupported architecture";
 
   # Syslinux (and isolinux) only supports x86-based architectures.
-  canx86BiosBoot = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+  canx86BiosBoot = pkgs.stdenv.hostPlatform.isx86;
 
 in
 
@@ -528,7 +528,7 @@ in
     };
 
     isoImage.contents = mkOption {
-      example = literalExample ''
+      example = literalExpression ''
         [ { source = pkgs.memtest86 + "/memtest.bin";
             target = "boot/memtest.bin";
           }
@@ -541,7 +541,7 @@ in
     };
 
     isoImage.storeContents = mkOption {
-      example = literalExample "[ pkgs.stdenv ]";
+      example = literalExpression "[ pkgs.stdenv ]";
       description = ''
         This option lists additional derivations to be included in the
         Nix store in the generated ISO image.
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
index 123f487baf93c..054c8c74a76b1 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
@@ -93,7 +93,7 @@ in
   boot.initrd.availableKernelModules =
     [ "vfat" "reiserfs" ];
 
-  boot.kernelPackages = pkgs.linuxPackages_3_10;
+  boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_10;
   boot.kernelParams = [ "console=tty1" ];
 
   boot.postBootCommands =
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
index a79209d7dfeff..674fb6c8a33c5 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
@@ -114,7 +114,7 @@ in
   # To be able to use the systemTarball to catch troubles.
   boot.crashDump = {
     enable = true;
-    kernelPackages = pkgs.linuxPackages_3_4;
+    kernelPackages = pkgs.linuxKernel.packages.linux_3_4;
   };
 
   # No grub for the tarball.
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
index 95579f3ca06d2..458e313a3f751 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -111,7 +111,7 @@ in
       # "console=ttyS0,115200n8"  # serial console
     ];
 
-  boot.kernelPackages = pkgs.linuxPackages_3_4;
+  boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_4;
 
   boot.supportedFilesystems = [ "reiserfs" ];
 
diff --git a/nixos/modules/installer/cd-dvd/system-tarball.nix b/nixos/modules/installer/cd-dvd/system-tarball.nix
index 58098c45535db..362c555cc53e8 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball.nix
@@ -15,7 +15,7 @@ in
 {
   options = {
     tarball.contents = mkOption {
-      example = literalExample ''
+      example = literalExpression ''
         [ { source = pkgs.memtest86 + "/memtest.bin";
             target = "boot/memtest.bin";
           }
@@ -28,7 +28,7 @@ in
     };
 
     tarball.storeContents = mkOption {
-      example = literalExample "[ pkgs.stdenv ]";
+      example = literalExpression "[ pkgs.stdenv ]";
       description = ''
         This option lists additional derivations to be included in the
         Nix store in the generated ISO image.
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 28b6c39b29df7..145f71b5d0c74 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -9,7 +9,7 @@ with lib;
   options = {
 
     netboot.storeContents = mkOption {
-      example = literalExample "[ pkgs.stdenv ]";
+      example = literalExpression "[ pkgs.stdenv ]";
       description = ''
         This option lists additional derivations to be included in the
         Nix store in the generated netboot image.
diff --git a/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix b/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix
index 83850f4c11587..103d6787a03c0 100644
--- a/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/sd-card/sd-image-raspberrypi.nix
@@ -12,7 +12,7 @@
   boot.loader.generic-extlinux-compatible.enable = true;
 
   boot.consoleLogLevel = lib.mkDefault 7;
-  boot.kernelPackages = pkgs.linuxPackages_rpi1;
+  boot.kernelPackages = pkgs.linuxKernel.packages.linux_rpi1;
 
   sdImage = {
     populateFirmwareCommands = let
diff --git a/nixos/modules/installer/sd-card/sd-image.nix b/nixos/modules/installer/sd-card/sd-image.nix
index 2a10a77300e85..a964cf2d6f851 100644
--- a/nixos/modules/installer/sd-card/sd-image.nix
+++ b/nixos/modules/installer/sd-card/sd-image.nix
@@ -49,7 +49,7 @@ in
 
     storePaths = mkOption {
       type = with types; listOf package;
-      example = literalExample "[ pkgs.stdenv ]";
+      example = literalExpression "[ pkgs.stdenv ]";
       description = ''
         Derivations to be included in the Nix store in the generated SD image.
       '';
@@ -107,7 +107,7 @@ in
     };
 
     populateFirmwareCommands = mkOption {
-      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
+      example = literalExpression "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
       description = ''
         Shell commands to populate the ./firmware directory.
         All files in that directory are copied to the
@@ -116,7 +116,7 @@ in
     };
 
     populateRootCommands = mkOption {
-      example = literalExample "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''";
+      example = literalExpression "''\${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
@@ -126,7 +126,7 @@ in
     };
 
     postBuildCommands = mkOption {
-      example = literalExample "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''";
+      example = literalExpression "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''";
       default = "";
       description = ''
         Shell commands to run after the image is built.
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 15c76287e34ed..065cea470fbb6 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/jhbxh1jwjc3hjhzs9y2hifdn0rmnfwaj-nix-2.3.15";
-  i686-linux = "/nix/store/9pspwnkdrgzma1l4xlv7arhwa56y16di-nix-2.3.15";
-  aarch64-linux = "/nix/store/72aqi5g7f4fhgvgafbcqwcpqjgnczj48-nix-2.3.15";
-  x86_64-darwin = "/nix/store/6p6qwp73dgfkqhynmxrzbx1lcfgfpqal-nix-2.3.15";
-  aarch64-darwin = "/nix/store/dmq2vksdhssgfl822shd0ky3x5x0klh4-nix-2.3.15";
+  x86_64-linux = "/nix/store/hapw7q1fkjxvprnkcgw9ppczavg4daj2-nix-2.4";
+  i686-linux = "/nix/store/8qlvh8pp5j8wgrzj3is2jlbhgrwgsiy9-nix-2.4";
+  aarch64-linux = "/nix/store/h48lkygcqj4hdibbdnpl67q7ks6vkrd6-nix-2.4";
+  x86_64-darwin = "/nix/store/c3mvzszvyzakvcp9spnjvsb8m2bpjk7m-nix-2.4";
+  aarch64-darwin = "/nix/store/hbfqs62r0hga2yr4zi5kc7fzhf71bq9n-nix-2.4";
 }
diff --git a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
index e49ceba242459..8aedce2fb49ce 100644
--- a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
+++ b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
@@ -8,11 +8,21 @@ let
     _file = "${networkExpr}@node-${vm}";
     imports = [ module ];
   }) (import networkExpr);
-in
 
-with import ../../../../lib/testing-python.nix {
-  inherit system;
   pkgs = import ../../../../.. { inherit system config; };
-};
 
-(makeTest { inherit nodes; testScript = ""; }).driverInteractive
+  testing = import ../../../../lib/testing-python.nix {
+    inherit system pkgs;
+  };
+
+  interactiveDriver = (testing.makeTest { inherit nodes; testScript = "start_all(); join_all();"; }).driverInteractive;
+in
+
+
+pkgs.runCommand "nixos-build-vms" { nativeBuildInputs = [ pkgs.makeWrapper ]; } ''
+  mkdir -p $out/bin
+  ln -s ${interactiveDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
+  ln -s ${interactiveDriver}/bin/nixos-test-driver $out/bin/nixos-run-vms
+  wrapProgram $out/bin/nixos-test-driver \
+    --add-flags "--interactive"
+''
diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh
index 450d776181489..00883205e8b5d 100644
--- a/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixos/modules/installer/tools/nixos-enter.sh
@@ -60,6 +60,35 @@ chmod 0755 "$mountPoint/dev" "$mountPoint/sys"
 mount --rbind /dev "$mountPoint/dev"
 mount --rbind /sys "$mountPoint/sys"
 
+# modified from https://github.com/archlinux/arch-install-scripts/blob/bb04ab435a5a89cd5e5ee821783477bc80db797f/arch-chroot.in#L26-L52
+chroot_add_resolv_conf() {
+    local chrootdir=$1 resolv_conf=$1/etc/resolv.conf
+
+    [[ -e /etc/resolv.conf ]] || return 0
+
+    # Handle resolv.conf as a symlink to somewhere else.
+    if [[ -L $chrootdir/etc/resolv.conf ]]; then
+      # readlink(1) should always give us *something* since we know at this point
+      # it's a symlink. For simplicity, ignore the case of nested symlinks.
+      # We also ignore the possibility if `../`s escaping the root.
+      resolv_conf=$(readlink "$chrootdir/etc/resolv.conf")
+      if [[ $resolv_conf = /* ]]; then
+        resolv_conf=$chrootdir$resolv_conf
+      else
+        resolv_conf=$chrootdir/etc/$resolv_conf
+      fi
+    fi
+
+    # ensure file exists to bind mount over
+    if [[ ! -f $resolv_conf ]]; then
+      install -Dm644 /dev/null "$resolv_conf" || return 1
+    fi
+
+    mount --bind /etc/resolv.conf "$resolv_conf"
+}
+
+chroot_add_resolv_conf "$mountPoint" || print "ERROR: failed to set up resolv.conf"
+
 (
     # If silent, write both stdout and stderr of activation script to /dev/null
     # otherwise, write both streams to stderr of this process
@@ -68,7 +97,7 @@ mount --rbind /sys "$mountPoint/sys"
     fi
 
     # Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings.
-    LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" chroot "$mountPoint" "$system/activate" 1>&2 || true
+    LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" IN_NIXOS_ENTER=1 chroot "$mountPoint" "$system/activate" 1>&2 || true
 
     # Create /tmp
     chroot "$mountPoint" systemd-tmpfiles --create --remove --exclude-prefix=/dev 1>&2 || true
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 7bc55e67134b6..fe8c4fb1a6b5f 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -91,6 +91,11 @@ sub hasCPUFeature {
 }
 
 
+sub cpuManufacturer {
+    my $id = shift;
+    return $cpuinfo =~ /^vendor_id\s*:.* $id$/m;
+}
+
 
 # Determine CPU governor to use
 if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
@@ -111,6 +116,9 @@ if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
 push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
 push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
 
+push @attrs, "hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "AuthenticAMD";
+push @attrs, "hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;" if cpuManufacturer "GenuineIntel";
+
 
 # Look at the PCI devices and add necessary modules.  Note that most
 # modules are auto-detected so we don't need to list them here.
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index f79ed3493dfb1..2f3b0cdd48f21 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -104,7 +104,20 @@ in
     };
   };
 
-  config = {
+  options.system.disableInstallerTools = mkOption {
+    internal = true;
+    type = types.bool;
+    default = false;
+    description = ''
+      Disable nixos-rebuild, nixos-generate-config, nixos-installer
+      and other NixOS tools. This is useful to shrink embedded,
+      read-only systems which are not expected to be rebuild or
+      reconfigure themselves. Use at your own risk!
+    '';
+  };
+
+  config = lib.mkIf (!config.system.disableInstallerTools) {
+
     system.nixos-generate-config.configuration = mkDefault ''
       # Edit this configuration file to define what should be installed on
       # your system.  Help is available in the configuration.nix(5) man page
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index f22ea5f9aeb8f..c3ded4f1ea340 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -6,7 +6,11 @@ let
 
   cfg = config.documentation;
 
-  manualModules = baseModules ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
+  manualModules =
+    baseModules
+    # Modules for which to show options even when not imported
+    ++ [ ../virtualisation/qemu-vm.nix ]
+    ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
 
   /* For the purpose of generating docs, evaluate options with each derivation
     in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
@@ -129,7 +133,7 @@ in
           extraOutputsToInstall = ["man"];
           ignoreCollisions = true;
         };
-        defaultText = "all man pages in config.environment.systemPackages";
+        defaultText = literalDocBook "all man pages in <option>config.environment.systemPackages</option>";
         description = ''
           The manual pages to generate caches for if <option>generateCaches</option>
           is enabled. Must be a path to a directory with man pages under
@@ -163,11 +167,11 @@ in
         description = ''
           Whether to install documentation targeted at developers.
           <itemizedlist>
-          <listitem><para>This includes man pages targeted at developers if <option>man.enable</option> is
+          <listitem><para>This includes man pages targeted at developers if <option>documentation.man.enable</option> is
                     set (this also includes "devman" outputs).</para></listitem>
-          <listitem><para>This includes info pages targeted at developers if <option>info.enable</option>
+          <listitem><para>This includes info pages targeted at developers if <option>documentation.info.enable</option>
                     is set (this also includes "devinfo" outputs).</para></listitem>
-          <listitem><para>This includes other pages targeted at developers if <option>doc.enable</option>
+          <listitem><para>This includes other pages targeted at developers if <option>documentation.doc.enable</option>
                     is set (this also includes "devdoc" outputs).</para></listitem>
           </itemizedlist>
         '';
@@ -181,10 +185,10 @@ in
           <itemizedlist>
           <listitem><para>This includes man pages like
                     <citerefentry><refentrytitle>configuration.nix</refentrytitle>
-                    <manvolnum>5</manvolnum></citerefentry> if <option>man.enable</option> is
+                    <manvolnum>5</manvolnum></citerefentry> if <option>documentation.man.enable</option> is
                     set.</para></listitem>
           <listitem><para>This includes the HTML manual and the <command>nixos-help</command> command if
-                    <option>doc.enable</option> is set.</para></listitem>
+                    <option>documentation.doc.enable</option> is set.</para></listitem>
           </itemizedlist>
         '';
       };
@@ -207,7 +211,7 @@ in
           Which extra NixOS module paths the generated NixOS's documentation should strip
           from options.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           # e.g. with options from modules in ''${pkgs.customModules}/nix:
           [ pkgs.customModules ]
         '';
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 02ae1390ce809..f392ca52566fb 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -46,9 +46,9 @@ in
       messagebus = 4; # D-Bus
       haldaemon = 5;
       #disk = 6; # unused
-      vsftpd = 7;
+      #vsftpd = 7; # dynamically allocated ass of 2021-09-14
       ftp = 8;
-      bitlbee = 9;
+      # bitlbee = 9; # removed 2021-10-05 #139765
       #avahi = 10; # removed 2019-05-22
       nagios = 11;
       atd = 12;
@@ -83,14 +83,14 @@ in
       #fourstore = 42; # dropped in 20.03
       #fourstorehttp = 43; # dropped in 20.03
       virtuoso = 44;
-      rtkit = 45;
+      #rtkit = 45; # dynamically allocated 2021-09-03
       dovecot2 = 46;
       dovenull2 = 47;
       prayer = 49;
       mpd = 50;
       clamav = 51;
       fprot = 52;
-      bind = 53;
+      # bind = 53; #dynamically allocated as of 2021-09-03
       wwwrun = 54;
       #adm = 55; # unused
       spamd = 56;
@@ -134,26 +134,26 @@ in
       firebird = 95;
       #keys = 96; # unused
       #haproxy = 97; # dynamically allocated as of 2020-03-11
-      mongodb = 98;
+      #mongodb = 98; #dynamically allocated as of 2021-09-03
       #openldap = 99; # dynamically allocated as of PR#94610
       #users = 100; # unused
-      cgminer = 101;
+      # cgminer = 101; #dynamically allocated as of 2021-09-17
       munin = 102;
-      logcheck = 103;
-      nix-ssh = 104;
+      #logcheck = 103; #dynamically allocated as of 2021-09-17
+      #nix-ssh = 104; #dynamically allocated as of 2021-09-03
       dictd = 105;
       couchdb = 106;
       #searx = 107; # dynamically allocated as of 2020-10-27
-      kippo = 108;
+      #kippo = 108; # removed 2021-10-07, the kippo package was removed in 1b213f321cdbfcf868b96fd9959c24207ce1b66a during 2021-04
       jenkins = 109;
       systemd-journal-gateway = 110;
       #notbit = 111; # unused
       aerospike = 111;
-      ngircd = 112;
+      #ngircd = 112; #dynamically allocated as of 2021-09-03
       #btsync = 113; # unused
-      minecraft = 114;
+      #minecraft = 114; #dynamically allocated as of 2021-09-03
       vault = 115;
-      rippled = 116;
+      # rippled = 116; #dynamically allocated as of 2021-09-18
       murmur = 117;
       foundationdb = 118;
       newrelic = 119;
@@ -169,19 +169,19 @@ in
       mopidy = 130;
       #docker = 131; # unused
       gdm = 132;
-      dhcpd = 133;
+      #dhcpd = 133; # dynamically allocated as of 2021-09-03
       siproxd = 134;
       mlmmj = 135;
-      neo4j = 136;
+      #neo4j = 136;# dynamically allocated as of 2021-09-03
       riemann = 137;
       riemanndash = 138;
-      radvd = 139;
-      zookeeper = 140;
-      dnsmasq = 141;
+      #radvd = 139;# dynamically allocated as of 2021-09-03
+      #zookeeper = 140;# dynamically allocated as of 2021-09-03
+      #dnsmasq = 141;# dynamically allocated as of 2021-09-03
       #uhub = 142; # unused
       yandexdisk = 143;
       mxisd = 144; # was once collectd
-      consul = 145;
+      #consul = 145;# dynamically allocated as of 2021-09-03
       mailpile = 146;
       redmine = 147;
       #seeks = 148; # removed 2020-06-21
@@ -192,7 +192,7 @@ in
       systemd-resolve = 153;
       systemd-timesync = 154;
       liquidsoap = 155;
-      etcd = 156;
+      #etcd = 156;# dynamically allocated as of 2021-09-03
       hbase = 158;
       opentsdb = 159;
       scollector = 160;
@@ -201,26 +201,26 @@ in
       peerflix = 163;
       #chronos = 164; # removed 2020-08-15
       gitlab = 165;
-      tox-bootstrapd = 166;
+      # tox-bootstrapd = 166; removed 2021-09-15
       cadvisor = 167;
       nylon = 168;
-      apache-kafka = 169;
+      #apache-kafka = 169;# dynamically allocated as of 2021-09-03
       #panamax = 170; # unused
       exim = 172;
       #fleet = 173; # unused
       #input = 174; # unused
       sddm = 175;
-      tss = 176;
+      #tss = 176; # dynamically allocated as of 2021-09-17
       #memcached = 177; removed 2018-01-03
-      ntp = 179;
+      #ntp = 179; # dynamically allocated as of 2021-09-17
       zabbix = 180;
       #redis = 181; removed 2018-01-03
-      unifi = 183;
+      #unifi = 183; dynamically allocated as of 2021-09-17
       uptimed = 184;
-      zope2 = 185;
-      ripple-data-api = 186;
+      #zope2 = 185; # dynamically allocated as of 2021-09-18
+      #ripple-data-api = 186; dynamically allocated as of 2021-09-17
       mediatomb = 187;
-      rdnssd = 188;
+      #rdnssd = 188; #dynamically allocated as of 2021-09-18
       ihaskell = 189;
       i2p = 190;
       lambdabot = 191;
@@ -231,20 +231,20 @@ in
       skydns = 197;
       # ripple-rest = 198; # unused, removed 2017-08-12
       # nix-serve = 199; # unused, removed 2020-12-12
-      tvheadend = 200;
+      #tvheadend = 200; # dynamically allocated as of 2021-09-18
       uwsgi = 201;
       gitit = 202;
       riemanntools = 203;
       subsonic = 204;
       riak = 205;
-      shout = 206;
+      #shout = 206; # dynamically allocated as of 2021-09-18
       gateone = 207;
       namecoin = 208;
       #lxd = 210; # unused
-      kibana = 211;
+      #kibana = 211;# dynamically allocated as of 2021-09-03
       xtreemfs = 212;
       calibre-server = 213;
-      heapster = 214;
+      #heapster = 214; #dynamically allocated as of 2021-09-17
       bepasty = 215;
       # pumpio = 216; # unused, removed 2018-02-24
       nm-openvpn = 217;
@@ -258,13 +258,13 @@ in
       rspamd = 225;
       # rmilter = 226; # unused, removed 2019-08-22
       cfdyndns = 227;
-      gammu-smsd = 228;
+      # gammu-smsd = 228; #dynamically allocated as of 2021-09-17
       pdnsd = 229;
       octoprint = 230;
       avahi-autoipd = 231;
-      nntp-proxy = 232;
+      # nntp-proxy = 232; #dynamically allocated as of 2021-09-17
       mjpg-streamer = 233;
-      radicale = 234;
+      #radicale = 234;# dynamically allocated as of 2021-09-03
       hydra-queue-runner = 235;
       hydra-www = 236;
       syncthing = 237;
@@ -272,14 +272,14 @@ in
       taskd = 240;
       # factorio = 241; # DynamicUser = true
       # emby = 242; # unusued, removed 2019-05-01
-      graylog = 243;
+      #graylog = 243;# dynamically allocated as of 2021-09-03
       sniproxy = 244;
       nzbget = 245;
       mosquitto = 246;
-      toxvpn = 247;
+      #toxvpn = 247; # dynamically allocated as of 2021-09-18
       # squeezelite = 248; # DynamicUser = true
       turnserver = 249;
-      smokeping = 250;
+      #smokeping = 250;# dynamically allocated as of 2021-09-03
       gocd-agent = 251;
       gocd-server = 252;
       terraria = 253;
@@ -366,9 +366,9 @@ in
       messagebus = 4; # D-Bus
       haldaemon = 5;
       disk = 6;
-      vsftpd = 7;
+      #vsftpd = 7; # dynamically allocated as of 2021-09-14
       ftp = 8;
-      bitlbee = 9;
+      # bitlbee = 9; # removed 2021-10-05 #139765
       #avahi = 10; # removed 2019-05-22
       #nagios = 11; # unused
       atd = 12;
@@ -462,7 +462,7 @@ in
       dictd = 105;
       couchdb = 106;
       #searx = 107; # dynamically allocated as of 2020-10-27
-      kippo = 108;
+      #kippo = 108; # removed 2021-10-07, the kippo package was removed in 1b213f321cdbfcf868b96fd9959c24207ce1b66a during 2021-04
       jenkins = 109;
       systemd-journal-gateway = 110;
       #notbit = 111; # unused
@@ -524,7 +524,7 @@ in
       #fleet = 173; # unused
       input = 174;
       sddm = 175;
-      tss = 176;
+      #tss = 176; #dynamically allocateda as of 2021-09-20
       #memcached = 177; # unused, removed 2018-01-03
       #ntp = 179; # unused
       zabbix = 180;
@@ -554,7 +554,7 @@ in
       #shout = 206; #unused
       gateone = 207;
       namecoin = 208;
-      lxd = 210; # unused
+      #lxd = 210; # unused
       #kibana = 211;
       xtreemfs = 212;
       calibre-server = 213;
@@ -573,7 +573,7 @@ in
       cfdyndns = 227;
       pdnsd = 229;
       octoprint = 230;
-      radicale = 234;
+      #radicale = 234;# dynamically allocated as of 2021-09-03
       syncthing = 237;
       caddy = 239;
       taskd = 240;
@@ -585,7 +585,7 @@ in
       #toxvpn = 247; # unused
       #squeezelite = 248; #unused
       turnserver = 249;
-      smokeping = 250;
+      #smokeping = 250;# dynamically allocated as of 2021-09-03
       gocd-agent = 251;
       gocd-server = 252;
       terraria = 253;
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index 1d2bc8c72813c..3638bebed931b 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -25,8 +25,8 @@ in {
     locate = mkOption {
       type = package;
       default = pkgs.findutils;
-      defaultText = "pkgs.findutils";
-      example = "pkgs.mlocate";
+      defaultText = literalExpression "pkgs.findutils";
+      example = literalExpression "pkgs.mlocate";
       description = ''
         The locate implementation to use
       '';
@@ -43,6 +43,9 @@ in {
         The format is described in
         <citerefentry><refentrytitle>systemd.time</refentrytitle>
         <manvolnum>7</manvolnum></citerefentry>.
+
+        To disable automatic updates, set to <literal>"never"</literal>
+        and run <command>updatedb</command> manually.
       '';
     };
 
@@ -146,7 +149,7 @@ in {
 
     prunePaths = mkOption {
       type = listOf path;
-      default = ["/tmp" "/var/tmp" "/var/cache" "/var/lock" "/var/run" "/var/spool" "/nix/store"];
+      default = [ "/tmp" "/var/tmp" "/var/cache" "/var/lock" "/var/run" "/var/spool" "/nix/store" "/nix/var/log/nix" ];
       description = ''
         Which paths to exclude from indexing
       '';
@@ -154,7 +157,7 @@ in {
 
     pruneNames = mkOption {
       type = listOf str;
-      default = [];
+      default = [ ".bzr" ".cache" ".git" ".hg" ".svn" ];
       description = ''
         Directory components which should exclude paths containing them from indexing
       '';
@@ -192,6 +195,18 @@ in {
       { LOCATE_PATH = cfg.output;
       };
 
+    environment.etc = {
+      # write /etc/updatedb.conf for manual calls to `updatedb`
+      "updatedb.conf" = {
+        text = ''
+          PRUNEFS="${lib.concatStringsSep " " cfg.pruneFS}"
+          PRUNENAMES="${lib.concatStringsSep " " cfg.pruneNames}"
+          PRUNEPATHS="${lib.concatStringsSep " " cfg.prunePaths}"
+          PRUNE_BIND_MOUNTSFR="${lib.boolToString cfg.pruneBindMounts}"
+        '';
+      };
+    };
+
     warnings = optional (isMLocate && cfg.localuser != null) "mlocate does not support the services.locate.localuser option; updatedb will run as root. (Silence with services.locate.localuser = null.)"
             ++ optional (isFindutils && cfg.pruneNames != []) "findutils locate does not support pruning by directory component"
             ++ optional (isFindutils && cfg.pruneBindMounts) "findutils locate does not support skipping bind mounts";
@@ -238,7 +253,7 @@ in {
         serviceConfig.ReadWritePaths = dirOf cfg.output;
       };
 
-    systemd.timers.update-locatedb =
+    systemd.timers.update-locatedb = mkIf (cfg.interval != "never")
       { description = "Update timer for locate database";
         partOf      = [ "update-locatedb.service" ];
         wantedBy    = [ "timers.target" ];
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index a2ac5c58528af..08bc4398555b7 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -67,13 +67,13 @@ in
   options.nixpkgs = {
 
     pkgs = mkOption {
-      defaultText = literalExample
-        ''import "''${nixos}/.." {
-            inherit (cfg) config overlays localSystem crossSystem;
-          }
-        '';
+      defaultText = literalExpression ''
+        import "''${nixos}/.." {
+          inherit (cfg) config overlays localSystem crossSystem;
+        }
+      '';
       type = pkgsType;
-      example = literalExample "import <nixpkgs> {}";
+      example = literalExpression "import <nixpkgs> {}";
       description = ''
         If set, the pkgs argument to all NixOS modules is the value of
         this option, extended with <code>nixpkgs.overlays</code>, if
@@ -109,7 +109,7 @@ in
 
     config = mkOption {
       default = {};
-      example = literalExample
+      example = literalExpression
         ''
           { allowBroken = true; allowUnfree = true; }
         '';
@@ -125,7 +125,7 @@ in
 
     overlays = mkOption {
       default = [];
-      example = literalExample
+      example = literalExpression
         ''
           [
             (self: super: {
@@ -158,7 +158,7 @@ in
       # Make sure that the final value has all fields for sake of other modules
       # referring to this. TODO make `lib.systems` itself use the module system.
       apply = lib.systems.elaborate;
-      defaultText = literalExample
+      defaultText = literalExpression
         ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
       description = ''
         Specifies the platform on which NixOS should be built. When
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 1998a309035b8..9eca0b8d65f23 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -48,6 +48,8 @@
   ./hardware/corectrl.nix
   ./hardware/digitalbitbox.nix
   ./hardware/device-tree.nix
+  ./hardware/gkraken.nix
+  ./hardware/flirc.nix
   ./hardware/i2c.nix
   ./hardware/sensor/hddtemp.nix
   ./hardware/sensor/iio.nix
@@ -127,6 +129,7 @@
   ./programs/cdemu.nix
   ./programs/chromium.nix
   ./programs/clickshare.nix
+  ./programs/cnping.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/criu.nix
   ./programs/dconf.nix
@@ -135,6 +138,7 @@
   ./programs/droidcam.nix
   ./programs/environment.nix
   ./programs/evince.nix
+  ./programs/extra-container.nix
   ./programs/feedbackd.nix
   ./programs/file-roller.nix
   ./programs/firejail.nix
@@ -145,6 +149,7 @@
   ./programs/fuse.nix
   ./programs/gamemode.nix
   ./programs/geary.nix
+  ./programs/git.nix
   ./programs/gnome-disks.nix
   ./programs/gnome-documents.nix
   ./programs/gnome-terminal.nix
@@ -152,6 +157,7 @@
   ./programs/gnupg.nix
   ./programs/gphoto2.nix
   ./programs/hamster.nix
+  ./programs/htop.nix
   ./programs/iftop.nix
   ./programs/iotop.nix
   ./programs/java.nix
@@ -170,6 +176,7 @@
   ./programs/npm.nix
   ./programs/noisetorch.nix
   ./programs/oblogout.nix
+  ./programs/pantheon-tweaks.nix
   ./programs/partition-manager.nix
   ./programs/plotinus.nix
   ./programs/proxychains.nix
@@ -200,6 +207,7 @@
   ./programs/vim.nix
   ./programs/wavemon.nix
   ./programs/waybar.nix
+  ./programs/weylus.nix
   ./programs/wireshark.nix
   ./programs/wshowkeys.nix
   ./programs/xfs_quota.nix
@@ -296,6 +304,7 @@
   ./services/cluster/kubernetes/pki.nix
   ./services/cluster/kubernetes/proxy.nix
   ./services/cluster/kubernetes/scheduler.nix
+  ./services/cluster/spark/default.nix
   ./services/computing/boinc/client.nix
   ./services/computing/foldingathome/client.nix
   ./services/computing/slurm/slurm.nix
@@ -340,6 +349,7 @@
   ./services/desktops/accountsservice.nix
   ./services/desktops/bamf.nix
   ./services/desktops/blueman.nix
+  ./services/desktops/cpupower-gui.nix
   ./services/desktops/dleyna-renderer.nix
   ./services/desktops/dleyna-server.nix
   ./services/desktops/pantheon/files.nix
@@ -382,6 +392,9 @@
   ./services/display-managers/greetd.nix
   ./services/editors/emacs.nix
   ./services/editors/infinoted.nix
+  ./services/finance/odoo.nix
+  ./services/games/crossfire-server.nix
+  ./services/games/deliantra-server.nix
   ./services/games/factorio.nix
   ./services/games/freeciv.nix
   ./services/games/minecraft-server.nix
@@ -403,12 +416,14 @@
   ./services/hardware/illum.nix
   ./services/hardware/interception-tools.nix
   ./services/hardware/irqbalance.nix
+  ./services/hardware/joycond.nix
   ./services/hardware/lcd.nix
   ./services/hardware/lirc.nix
   ./services/hardware/nvidia-optimus.nix
   ./services/hardware/pcscd.nix
   ./services/hardware/pommed.nix
   ./services/hardware/power-profiles-daemon.nix
+  ./services/hardware/rasdaemon.nix
   ./services/hardware/ratbagd.nix
   ./services/hardware/sane.nix
   ./services/hardware/sane_extra_backends/brscan4.nix
@@ -471,6 +486,9 @@
   ./services/mail/roundcube.nix
   ./services/mail/sympa.nix
   ./services/mail/nullmailer.nix
+  ./services/matrix/mjolnir.nix
+  ./services/matrix/pantalaimon.nix
+  ./services/misc/ananicy.nix
   ./services/misc/airsonic.nix
   ./services/misc/ankisyncd.nix
   ./services/misc/apache-kafka.nix
@@ -534,6 +552,7 @@
   ./services/misc/matrix-appservice-discord.nix
   ./services/misc/matrix-appservice-irc.nix
   ./services/misc/matrix-synapse.nix
+  ./services/misc/mautrix-facebook.nix
   ./services/misc/mautrix-telegram.nix
   ./services/misc/mbpfan.nix
   ./services/misc/mediatomb.nix
@@ -553,12 +572,14 @@
   ./services/misc/octoprint.nix
   ./services/misc/ombi.nix
   ./services/misc/osrm.nix
+  ./services/misc/owncast.nix
   ./services/misc/packagekit.nix
   ./services/misc/paperless-ng.nix
   ./services/misc/parsoid.nix
   ./services/misc/plex.nix
   ./services/misc/plikd.nix
   ./services/misc/podgrab.nix
+  ./services/misc/prowlarr.nix
   ./services/misc/tautulli.nix
   ./services/misc/pinnwand.nix
   ./services/misc/pykms.nix
@@ -570,6 +591,7 @@
   ./services/misc/safeeyes.nix
   ./services/misc/sdrplay.nix
   ./services/misc/sickbeard.nix
+  ./services/misc/signald.nix
   ./services/misc/siproxd.nix
   ./services/misc/snapper.nix
   ./services/misc/sonarr.nix
@@ -584,10 +606,12 @@
   ./services/misc/sysprof.nix
   ./services/misc/taskserver
   ./services/misc/tiddlywiki.nix
+  ./services/misc/tp-auto-kbbl.nix
   ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
   ./services/misc/weechat.nix
   ./services/misc/xmr-stak.nix
+  ./services/misc/xmrig.nix
   ./services/misc/zigbee2mqtt.nix
   ./services/misc/zoneminder.nix
   ./services/misc/zookeeper.nix
@@ -618,6 +642,7 @@
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
+  ./services/monitoring/parsedmarc.nix
   ./services/monitoring/prometheus/default.nix
   ./services/monitoring/prometheus/alertmanager.nix
   ./services/monitoring/prometheus/exporters.nix
@@ -659,12 +684,14 @@
   ./services/network-filesystems/tahoe.nix
   ./services/network-filesystems/diod.nix
   ./services/network-filesystems/u9fs.nix
+  ./services/network-filesystems/webdav.nix
   ./services/network-filesystems/yandex-disk.nix
   ./services/network-filesystems/xtreemfs.nix
   ./services/network-filesystems/ceph.nix
   ./services/networking/3proxy.nix
   ./services/networking/adguardhome.nix
   ./services/networking/amuled.nix
+  ./services/networking/antennas.nix
   ./services/networking/aria2.nix
   ./services/networking/asterisk.nix
   ./services/networking/atftpd.nix
@@ -740,18 +767,19 @@
   ./services/networking/iscsi/root-initiator.nix
   ./services/networking/iscsi/target.nix
   ./services/networking/iwd.nix
+  ./services/networking/jibri/default.nix
   ./services/networking/jicofo.nix
   ./services/networking/jitsi-videobridge.nix
   ./services/networking/kea.nix
   ./services/networking/keepalived/default.nix
   ./services/networking/keybase.nix
-  ./services/networking/kippo.nix
   ./services/networking/knot.nix
   ./services/networking/kresd.nix
   ./services/networking/lambdabot.nix
   ./services/networking/libreswan.nix
   ./services/networking/lldpd.nix
   ./services/networking/logmein-hamachi.nix
+  ./services/networking/lxd-image-server.nix
   ./services/networking/mailpile.nix
   ./services/networking/magic-wormhole-mailbox-server.nix
   ./services/networking/matterbridge.nix
@@ -765,6 +793,7 @@
   ./services/networking/mstpd.nix
   ./services/networking/mtprotoproxy.nix
   ./services/networking/mullvad-vpn.nix
+  ./services/networking/multipath.nix
   ./services/networking/murmur.nix
   ./services/networking/mxisd.nix
   ./services/networking/namecoind.nix
@@ -821,6 +850,7 @@
   ./services/networking/rpcbind.nix
   ./services/networking/rxe.nix
   ./services/networking/sabnzbd.nix
+  ./services/networking/seafile.nix
   ./services/networking/searx.nix
   ./services/networking/skydns.nix
   ./services/networking/shadowsocks.nix
@@ -869,11 +899,9 @@
   ./services/video/unifi-video.nix
   ./services/networking/v2ray.nix
   ./services/networking/vsftpd.nix
-  ./services/networking/wakeonlan.nix
   ./services/networking/wasabibackend.nix
   ./services/networking/websockify.nix
   ./services/networking/wg-quick.nix
-  ./services/networking/wicd.nix
   ./services/networking/wireguard.nix
   ./services/networking/wpa_supplicant.nix
   ./services/networking/xandikos.nix
@@ -894,6 +922,7 @@
   ./services/search/elasticsearch-curator.nix
   ./services/search/hound.nix
   ./services/search/kibana.nix
+  ./services/search/meilisearch.nix
   ./services/search/solr.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
@@ -910,6 +939,7 @@
   ./services/security/nginx-sso.nix
   ./services/security/oauth2_proxy.nix
   ./services/security/oauth2_proxy_nginx.nix
+  ./services/security/opensnitch.nix
   ./services/security/privacyidea.nix
   ./services/security/physlock.nix
   ./services/security/shibboleth-sp.nix
@@ -951,8 +981,10 @@
   ./services/web-apps/atlassian/jira.nix
   ./services/web-apps/bookstack.nix
   ./services/web-apps/calibre-web.nix
+  ./services/web-apps/code-server.nix
   ./services/web-apps/convos.nix
   ./services/web-apps/cryptpad.nix
+  ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/dokuwiki.nix
@@ -971,6 +1003,8 @@
   ./services/web-apps/jirafeau.nix
   ./services/web-apps/jitsi-meet.nix
   ./services/web-apps/keycloak.nix
+  ./services/web-apps/lemmy.nix
+  ./services/web-apps/invidious.nix
   ./services/web-apps/limesurvey.nix
   ./services/web-apps/mastodon.nix
   ./services/web-apps/mattermost.nix
@@ -980,11 +1014,14 @@
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/node-red.nix
+  ./services/web-apps/pict-rs.nix
+  ./services/web-apps/peertube.nix
   ./services/web-apps/plantuml-server.nix
   ./services/web-apps/plausible.nix
   ./services/web-apps/pgpkeyserver-lite.nix
   ./services/web-apps/matomo.nix
   ./services/web-apps/moinmoin.nix
+  ./services/web-apps/openwebrx.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/sogo.nix
   ./services/web-apps/rss-bridge.nix
@@ -1022,7 +1059,7 @@
   ./services/web-servers/shellinabox.nix
   ./services/web-servers/tomcat.nix
   ./services/web-servers/traefik.nix
-  ./services/web-servers/trafficserver.nix
+  ./services/web-servers/trafficserver/default.nix
   ./services/web-servers/ttyd.nix
   ./services/web-servers/uwsgi.nix
   ./services/web-servers/varnish/default.nix
@@ -1051,6 +1088,7 @@
   ./services/x11/gdk-pixbuf.nix
   ./services/x11/imwheel.nix
   ./services/x11/redshift.nix
+  ./services/x11/touchegg.nix
   ./services/x11/urserver.nix
   ./services/x11/urxvtd.nix
   ./services/x11/window-managers/awesome.nix
@@ -1159,6 +1197,7 @@
   ./virtualisation/virtualbox-guest.nix
   ./virtualisation/virtualbox-host.nix
   ./virtualisation/vmware-guest.nix
+  ./virtualisation/waydroid.nix
   ./virtualisation/xen-dom0.nix
   ./virtualisation/xe-guest-utilities.nix
 ]
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 3b67d628f9fd7..33dd80d7c5abd 100644
--- a/nixos/modules/profiles/base.nix
+++ b/nixos/modules/profiles/base.nix
@@ -40,6 +40,7 @@
     # Tools to create / manipulate filesystems.
     pkgs.ntfsprogs # for resizing NTFS partitions
     pkgs.dosfstools
+    pkgs.mtools
     pkgs.xfsprogs.bin
     pkgs.jfsutils
     pkgs.f2fs-tools
diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix
index f044e6f39ea5a..e79b927238419 100644
--- a/nixos/modules/profiles/minimal.nix
+++ b/nixos/modules/profiles/minimal.nix
@@ -14,4 +14,6 @@ with lib;
   documentation.enable = mkDefault false;
 
   documentation.nixos.enable = mkDefault false;
+
+  programs.command-not-found.enable = mkDefault false;
 }
diff --git a/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index b45eb16e3eaf6..ad75ab27666ce 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -19,7 +19,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.atop;
-        defaultText = "pkgs.atop";
+        defaultText = literalExpression "pkgs.atop";
         description = ''
           Which package to use for Atop.
         '';
@@ -37,7 +37,7 @@ in
         package = mkOption {
           type = types.package;
           default = config.boot.kernelPackages.netatop;
-          defaultText = "config.boot.kernelPackages.netatop";
+          defaultText = literalExpression "config.boot.kernelPackages.netatop";
           description = ''
             Which package to use for netatop.
           '';
@@ -141,8 +141,15 @@ in
             // mkService cfg.atopgpu.enable "atopgpu" [ atop ];
           timers = mkTimer cfg.atopRotateTimer.enable "atop-rotate" [ atop ];
         };
-      security.wrappers =
-        lib.mkIf cfg.setuidWrapper.enable { atop = { source = "${atop}/bin/atop"; }; };
+
+      security.wrappers = lib.mkIf cfg.setuidWrapper.enable {
+        atop =
+          { setuid = true;
+            owner = "root";
+            group = "root";
+            source = "${atop}/bin/atop";
+          };
+      };
     }
   );
 }
diff --git a/nixos/modules/programs/bandwhich.nix b/nixos/modules/programs/bandwhich.nix
index 1cffb5fa2765c..610d602ad2cc9 100644
--- a/nixos/modules/programs/bandwhich.nix
+++ b/nixos/modules/programs/bandwhich.nix
@@ -22,8 +22,10 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs; [ bandwhich ];
     security.wrappers.bandwhich = {
-      source = "${pkgs.bandwhich}/bin/bandwhich";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_raw,cap_net_admin+ep";
+      source = "${pkgs.bandwhich}/bin/bandwhich";
     };
   };
 }
diff --git a/nixos/modules/programs/captive-browser.nix b/nixos/modules/programs/captive-browser.nix
index d7684d08c6c77..0f5d087e8d87e 100644
--- a/nixos/modules/programs/captive-browser.nix
+++ b/nixos/modules/programs/captive-browser.nix
@@ -14,7 +14,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.captive-browser;
-        defaultText = "pkgs.captive-browser";
+        defaultText = literalExpression "pkgs.captive-browser";
         description = "Which package to use for captive-browser";
       };
 
@@ -105,11 +105,15 @@ in
       );
 
     security.wrappers.udhcpc = {
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_raw+p";
       source = "${pkgs.busybox}/bin/udhcpc";
     };
 
     security.wrappers.captive-browser = {
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_raw+p";
       source = pkgs.writeShellScript "captive-browser" ''
         export PREV_CONFIG_HOME="$XDG_CONFIG_HOME"
diff --git a/nixos/modules/programs/ccache.nix b/nixos/modules/programs/ccache.nix
index d672e1da017a8..0f7fd0a3683c1 100644
--- a/nixos/modules/programs/ccache.nix
+++ b/nixos/modules/programs/ccache.nix
@@ -28,7 +28,9 @@ in {
 
       # "nix-ccache --show-stats" and "nix-ccache --clear"
       security.wrappers.nix-ccache = {
+        owner = "root";
         group = "nixbld";
+        setuid = false;
         setgid = true;
         source = pkgs.writeScript "nix-ccache.pl" ''
           #!${pkgs.perl}/bin/perl
diff --git a/nixos/modules/programs/chromium.nix b/nixos/modules/programs/chromium.nix
index b727f850a949b..602253a321d78 100644
--- a/nixos/modules/programs/chromium.nix
+++ b/nixos/modules/programs/chromium.nix
@@ -33,7 +33,7 @@ in
           for additional details.
         '';
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [
             "chlffgpmiacpedhhbkiomidkjlcfhogd" # pushbullet
             "mbniclmhobmnbdlbpiphghaielnnpgdp" # lightshot
@@ -75,7 +75,7 @@ in
           Make sure the selected policy is supported on Linux and your browser version.
         '';
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "BrowserSignin" = 0;
             "SyncDisabled" = true;
diff --git a/nixos/modules/programs/cnping.nix b/nixos/modules/programs/cnping.nix
new file mode 100644
index 0000000000000..d208d2b070407
--- /dev/null
+++ b/nixos/modules/programs/cnping.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.cnping;
+in
+{
+  options = {
+    programs.cnping = {
+      enable = mkEnableOption "Whether to install a setcap wrapper for cnping";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers.cnping = {
+      source = "${pkgs.cnping}/bin/cnping";
+      capabilities = "cap_net_raw+ep";
+    };
+  };
+}
diff --git a/nixos/modules/programs/command-not-found/command-not-found.pl b/nixos/modules/programs/command-not-found/command-not-found.pl
index 6e275bcc8be6c..220d057b7f4f4 100644
--- a/nixos/modules/programs/command-not-found/command-not-found.pl
+++ b/nixos/modules/programs/command-not-found/command-not-found.pl
@@ -25,14 +25,7 @@ if (!defined $res || scalar @$res == 0) {
     print STDERR "$program: command not found\n";
 } elsif (scalar @$res == 1) {
     my $package = @$res[0]->{package};
-    if ($ENV{"NIX_AUTO_INSTALL"} // "") {
-        print STDERR <<EOF;
-The program '$program' is currently not installed. It is provided by
-the package '$package', which I will now install for you.
-EOF
-        ;
-        exit 126 if system("nix-env", "-iA", "nixos.$package") == 0;
-    } elsif ($ENV{"NIX_AUTO_RUN"} // "") {
+    if ($ENV{"NIX_AUTO_RUN"} // "") {
         exec("nix-shell", "-p", $package, "--run", shell_quote("exec", @ARGV));
     } else {
         print STDERR <<EOF;
diff --git a/nixos/modules/programs/digitalbitbox/default.nix b/nixos/modules/programs/digitalbitbox/default.nix
index 2fe0a14412c51..cabdf260cda3d 100644
--- a/nixos/modules/programs/digitalbitbox/default.nix
+++ b/nixos/modules/programs/digitalbitbox/default.nix
@@ -19,7 +19,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.digitalbitbox;
-      defaultText = "pkgs.digitalbitbox";
+      defaultText = literalExpression "pkgs.digitalbitbox";
       description = "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
     };
   };
diff --git a/nixos/modules/programs/dmrconfig.nix b/nixos/modules/programs/dmrconfig.nix
index e48a4f3183708..d2a5117c48ef2 100644
--- a/nixos/modules/programs/dmrconfig.nix
+++ b/nixos/modules/programs/dmrconfig.nix
@@ -24,7 +24,7 @@ in {
       package = mkOption {
         default = pkgs.dmrconfig;
         type = types.package;
-        defaultText = "pkgs.dmrconfig";
+        defaultText = literalExpression "pkgs.dmrconfig";
         description = "dmrconfig derivation to use";
       };
     };
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index 39010323f61e8..d552c751afd73 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -18,12 +18,16 @@ in
 
     environment.variables =
       { NIXPKGS_CONFIG = "/etc/nix/nixpkgs-config.nix";
+        # note: many programs exec() this directly, so default options for less must not
+        # be specified here; do so in the default value of programs.less.envVariables instead
         PAGER = mkDefault "less";
-        LESS = mkDefault "-R";
         EDITOR = mkDefault "nano";
         XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
       };
 
+    # since we set PAGER to this above, make sure it's installed
+    programs.less.enable = true;
+
     environment.profiles = mkAfter
       [ "/nix/var/nix/profiles/default"
         "/run/current-system/sw"
diff --git a/nixos/modules/programs/evince.nix b/nixos/modules/programs/evince.nix
index 473fddb09d02e..c033230afb102 100644
--- a/nixos/modules/programs/evince.nix
+++ b/nixos/modules/programs/evince.nix
@@ -4,7 +4,9 @@
 
 with lib;
 
-{
+let cfg = config.programs.evince;
+
+in {
 
   # Added 2019-08-09
   imports = [
@@ -22,6 +24,13 @@ with lib;
       enable = mkEnableOption
         "Evince, the GNOME document viewer";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.evince;
+        defaultText = literalExpression "pkgs.evince";
+        description = "Evince derivation to use.";
+      };
+
     };
 
   };
@@ -31,11 +40,11 @@ with lib;
 
   config = mkIf config.programs.evince.enable {
 
-    environment.systemPackages = [ pkgs.evince ];
+    environment.systemPackages = [ cfg.package ];
 
-    services.dbus.packages = [ pkgs.evince ];
+    services.dbus.packages = [ cfg.package ];
 
-    systemd.packages = [ pkgs.evince ];
+    systemd.packages = [ cfg.package ];
 
   };
 
diff --git a/nixos/modules/programs/extra-container.nix b/nixos/modules/programs/extra-container.nix
new file mode 100644
index 0000000000000..c10ccd7691688
--- /dev/null
+++ b/nixos/modules/programs/extra-container.nix
@@ -0,0 +1,17 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.programs.extra-container;
+in {
+  options = {
+    programs.extra-container.enable = mkEnableOption ''
+      extra-container, a tool for running declarative NixOS containers
+      without host system rebuilds
+    '';
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.extra-container ];
+    boot.extraSystemdUnitPaths = [ "/etc/systemd-mutable/system" ];
+  };
+}
diff --git a/nixos/modules/programs/feedbackd.nix b/nixos/modules/programs/feedbackd.nix
index bb14489a6f4dc..4194080c8a734 100644
--- a/nixos/modules/programs/feedbackd.nix
+++ b/nixos/modules/programs/feedbackd.nix
@@ -18,6 +18,7 @@ in {
         '';
         type = types.package;
         default = pkgs.feedbackd;
+        defaultText = literalExpression "pkgs.feedbackd";
       };
     };
   };
diff --git a/nixos/modules/programs/file-roller.nix b/nixos/modules/programs/file-roller.nix
index b939d59909c0d..3c47d59816548 100644
--- a/nixos/modules/programs/file-roller.nix
+++ b/nixos/modules/programs/file-roller.nix
@@ -4,7 +4,9 @@
 
 with lib;
 
-{
+let cfg = config.programs.file-roller;
+
+in {
 
   # Added 2019-08-09
   imports = [
@@ -21,6 +23,13 @@ with lib;
 
       enable = mkEnableOption "File Roller, an archive manager for GNOME";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.gnome.file-roller;
+        defaultText = literalExpression "pkgs.gnome.file-roller";
+        description = "File Roller derivation to use.";
+      };
+
     };
 
   };
@@ -28,11 +37,11 @@ with lib;
 
   ###### implementation
 
-  config = mkIf config.programs.file-roller.enable {
+  config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.gnome.file-roller ];
+    environment.systemPackages = [ cfg.package ];
 
-    services.dbus.packages = [ pkgs.gnome.file-roller ];
+    services.dbus.packages = [ cfg.package ];
 
   };
 
diff --git a/nixos/modules/programs/firejail.nix b/nixos/modules/programs/firejail.nix
index ad4ef1a39459d..41db4f0136efd 100644
--- a/nixos/modules/programs/firejail.nix
+++ b/nixos/modules/programs/firejail.nix
@@ -40,13 +40,13 @@ in {
           executable = mkOption {
             type = types.path;
             description = "Executable to run sandboxed";
-            example = literalExample "''${lib.getBin pkgs.firefox}/bin/firefox";
+            example = literalExpression ''"''${lib.getBin pkgs.firefox}/bin/firefox"'';
           };
           profile = mkOption {
             type = types.nullOr types.path;
             default = null;
             description = "Profile to use";
-            example = literalExample "''${pkgs.firejail}/etc/firejail/firefox.profile";
+            example = literalExpression ''"''${pkgs.firejail}/etc/firejail/firefox.profile"'';
           };
           extraArgs = mkOption {
             type = types.listOf types.str;
@@ -57,7 +57,7 @@ in {
         };
       }));
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           firefox = {
             executable = "''${lib.getBin pkgs.firefox}/bin/firefox";
@@ -81,7 +81,12 @@ in {
   };
 
   config = mkIf cfg.enable {
-    security.wrappers.firejail.source = "${lib.getBin pkgs.firejail}/bin/firejail";
+    security.wrappers.firejail =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${lib.getBin pkgs.firejail}/bin/firejail";
+      };
 
     environment.systemPackages = [ pkgs.firejail ] ++ [ wrappedBins ];
   };
diff --git a/nixos/modules/programs/flexoptix-app.nix b/nixos/modules/programs/flexoptix-app.nix
index 93dcdfeb51473..5e169be2d8933 100644
--- a/nixos/modules/programs/flexoptix-app.nix
+++ b/nixos/modules/programs/flexoptix-app.nix
@@ -13,7 +13,7 @@ in {
         description = "FLEXOPTIX app package to use";
         type = types.package;
         default = pkgs.flexoptix-app;
-        defaultText = "\${pkgs.flexoptix-app}";
+        defaultText = literalExpression "pkgs.flexoptix-app";
       };
     };
   };
diff --git a/nixos/modules/programs/freetds.nix b/nixos/modules/programs/freetds.nix
index b4b657e391bf9..d95c44d756afb 100644
--- a/nixos/modules/programs/freetds.nix
+++ b/nixos/modules/programs/freetds.nix
@@ -17,7 +17,7 @@ in
     environment.freetds = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         { MYDATABASE = '''
             host = 10.0.2.100
             port = 1433
diff --git a/nixos/modules/programs/gamemode.nix b/nixos/modules/programs/gamemode.nix
index 03949bf98df6a..a377a1619aa04 100644
--- a/nixos/modules/programs/gamemode.nix
+++ b/nixos/modules/programs/gamemode.nix
@@ -23,7 +23,7 @@ in
           System-wide configuration for GameMode (/etc/gamemode.ini).
           See gamemoded(8) man page for available settings.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             general = {
               renice = 10;
@@ -56,6 +56,8 @@ in
       polkit.enable = true;
       wrappers = mkIf cfg.enableRenice {
         gamemoded = {
+          owner = "root";
+          group = "root";
           source = "${pkgs.gamemode}/bin/gamemoded";
           capabilities = "cap_sys_nice+ep";
         };
diff --git a/nixos/modules/programs/git.nix b/nixos/modules/programs/git.nix
new file mode 100644
index 0000000000000..06ce374b19923
--- /dev/null
+++ b/nixos/modules/programs/git.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.git;
+in
+
+{
+  options = {
+    programs.git = {
+      enable = mkEnableOption "git";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.git;
+        defaultText = literalExpression "pkgs.git";
+        example = literalExpression "pkgs.gitFull";
+        description = "The git package to use";
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (attrsOf anything);
+        default = { };
+        example = {
+          init.defaultBranch = "main";
+          url."https://github.com/".insteadOf = [ "gh:" "github:" ];
+        };
+        description = ''
+          Configuration to write to /etc/gitconfig. See the CONFIGURATION FILE
+          section of git-config(1) for more information.
+        '';
+      };
+
+      lfs = {
+        enable = mkEnableOption "git-lfs";
+
+        package = mkOption {
+          type = types.package;
+          default = pkgs.git-lfs;
+          defaultText = literalExpression "pkgs.git-lfs";
+          description = "The git-lfs package to use";
+        };
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      environment.systemPackages = [ cfg.package ];
+      environment.etc.gitconfig = mkIf (cfg.config != {}) {
+        text = generators.toGitINI cfg.config;
+      };
+    })
+    (mkIf (cfg.enable && cfg.lfs.enable) {
+      environment.systemPackages = [ cfg.lfs.package ];
+      programs.git.config = {
+        filter.lfs = {
+          clean = "git-lfs clean -- %f";
+          smudge = "git-lfs smudge -- %f";
+          process = "git-lfs filter-process";
+          required = true;
+        };
+      };
+    })
+  ];
+
+  meta.maintainers = with maintainers; [ figsoda ];
+}
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index ce8799b21d692..06f49182e4df1 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -27,7 +27,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.gnupg;
-      defaultText = "pkgs.gnupg";
+      defaultText = literalExpression "pkgs.gnupg";
       description = ''
         The gpg package that should be used.
       '';
diff --git a/nixos/modules/programs/htop.nix b/nixos/modules/programs/htop.nix
new file mode 100644
index 0000000000000..5c197838e47c9
--- /dev/null
+++ b/nixos/modules/programs/htop.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs.htop;
+
+  fmt = value:
+    if isList value then concatStringsSep " " (map fmt value) else
+    if isString value then value else
+    if isBool value || isInt value then toString value else
+    throw "Unrecognized type ${typeOf value} in htop settings";
+
+in
+
+{
+
+  options.programs.htop = {
+    package = mkOption {
+      type = types.package;
+      default = pkgs.htop;
+      defaultText = "pkgs.htop";
+      description = ''
+        The htop package that should be used.
+      '';
+    };
+
+    enable = mkEnableOption "htop process monitor";
+
+    settings = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool (listOf (oneOf [ str int bool ])) ]);
+      default = {};
+      example = {
+        hide_kernel_threads = true;
+        hide_userland_threads = true;
+      };
+      description = ''
+        Extra global default configuration for htop
+        which is read on first startup only.
+        Htop subsequently uses ~/.config/htop/htoprc
+        as configuration source.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      cfg.package
+    ];
+
+    environment.etc."htoprc".text = ''
+      # Global htop configuration
+      # To change set: programs.htop.settings.KEY = VALUE;
+    '' + concatStringsSep "\n" (mapAttrsToList (key: value: "${key}=${fmt value}") cfg.settings);
+  };
+
+}
diff --git a/nixos/modules/programs/iftop.nix b/nixos/modules/programs/iftop.nix
index a98a9a8187d4b..c74714a9a6d64 100644
--- a/nixos/modules/programs/iftop.nix
+++ b/nixos/modules/programs/iftop.nix
@@ -11,8 +11,10 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.iftop ];
     security.wrappers.iftop = {
-      source = "${pkgs.iftop}/bin/iftop";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_raw+p";
+      source = "${pkgs.iftop}/bin/iftop";
     };
   };
 }
diff --git a/nixos/modules/programs/iotop.nix b/nixos/modules/programs/iotop.nix
index 5512dbc62f72b..b7c1c69f9ddd0 100644
--- a/nixos/modules/programs/iotop.nix
+++ b/nixos/modules/programs/iotop.nix
@@ -10,8 +10,10 @@ in {
   };
   config = mkIf cfg.enable {
     security.wrappers.iotop = {
-      source = "${pkgs.iotop}/bin/iotop";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_admin+p";
+      source = "${pkgs.iotop}/bin/iotop";
     };
   };
 }
diff --git a/nixos/modules/programs/java.nix b/nixos/modules/programs/java.nix
index d31698c3b392d..4e4e0629e5d97 100644
--- a/nixos/modules/programs/java.nix
+++ b/nixos/modules/programs/java.nix
@@ -34,7 +34,7 @@ in
 
       package = mkOption {
         default = pkgs.jdk;
-        defaultText = "pkgs.jdk";
+        defaultText = literalExpression "pkgs.jdk";
         description = ''
           Java package to install. Typical values are pkgs.jdk or pkgs.jre.
         '';
diff --git a/nixos/modules/programs/kbdlight.nix b/nixos/modules/programs/kbdlight.nix
index 58e45872fac87..8a2a0057cf2da 100644
--- a/nixos/modules/programs/kbdlight.nix
+++ b/nixos/modules/programs/kbdlight.nix
@@ -11,6 +11,11 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.kbdlight ];
-    security.wrappers.kbdlight.source = "${pkgs.kbdlight.out}/bin/kbdlight";
+    security.wrappers.kbdlight =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.kbdlight.out}/bin/kbdlight";
+      };
   };
 }
diff --git a/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix
index 673449b9f6338..df698e84dd702 100644
--- a/nixos/modules/programs/kdeconnect.nix
+++ b/nixos/modules/programs/kdeconnect.nix
@@ -13,9 +13,9 @@ with lib;
     '';
     package = mkOption {
       default = pkgs.kdeconnect;
-      defaultText = "pkgs.kdeconnect";
+      defaultText = literalExpression "pkgs.kdeconnect";
       type = types.package;
-      example = literalExample "pkgs.gnomeExtensions.gsconnect";
+      example = literalExpression "pkgs.gnomeExtensions.gsconnect";
       description = ''
         The package providing the implementation for kdeconnect.
       '';
diff --git a/nixos/modules/programs/less.nix b/nixos/modules/programs/less.nix
index 09cb6030e6616..794146b19faf5 100644
--- a/nixos/modules/programs/less.nix
+++ b/nixos/modules/programs/less.nix
@@ -24,9 +24,7 @@ let
     }
   '';
 
-  lessKey = pkgs.runCommand "lesskey"
-            { src = pkgs.writeText "lessconfig" configText; preferLocalBuild = true; }
-            "${pkgs.less}/bin/lesskey -o $out $src";
+  lessKey = pkgs.writeText "lessconfig" configText;
 
 in
 
@@ -35,12 +33,14 @@ in
 
     programs.less = {
 
+      # note that environment.nix sets PAGER=less, and
+      # therefore also enables this module
       enable = mkEnableOption "less";
 
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        example = literalExample "\${pkgs.my-configs}/lesskey";
+        example = literalExpression ''"''${pkgs.my-configs}/lesskey"'';
         description = ''
           Path to lesskey configuration file.
 
@@ -81,7 +81,9 @@ in
 
       envVariables = mkOption {
         type = types.attrsOf types.str;
-        default = {};
+        default = {
+          LESS = "-R";
+        };
         example = {
           LESS = "--quit-if-one-screen";
         };
@@ -91,6 +93,7 @@ in
       lessopen = mkOption {
         type = types.nullOr types.str;
         default = "|${pkgs.lesspipe}/bin/lesspipe.sh %s";
+        defaultText = literalExpression ''"|''${pkgs.lesspipe}/bin/lesspipe.sh %s"'';
         description = ''
           Before less opens a file, it first gives your input preprocessor a chance to modify the way the contents of the file are displayed.
         '';
@@ -111,7 +114,7 @@ in
     environment.systemPackages = [ pkgs.less ];
 
     environment.variables = {
-      LESSKEY_SYSTEM = toString lessKey;
+      LESSKEYIN_SYSTEM = toString lessKey;
     } // optionalAttrs (cfg.lessopen != null) {
       LESSOPEN = cfg.lessopen;
     } // optionalAttrs (cfg.lessclose != null) {
diff --git a/nixos/modules/programs/liboping.nix b/nixos/modules/programs/liboping.nix
index 4e4c235ccde4a..4433f9767d6ee 100644
--- a/nixos/modules/programs/liboping.nix
+++ b/nixos/modules/programs/liboping.nix
@@ -13,8 +13,10 @@ in {
     security.wrappers = mkMerge (map (
       exec: {
         "${exec}" = {
-          source = "${pkgs.liboping}/bin/${exec}";
+          owner = "root";
+          group = "root";
           capabilities = "cap_net_raw+p";
+          source = "${pkgs.liboping}/bin/${exec}";
         };
       }
     ) [ "oping" "noping" ]);
diff --git a/nixos/modules/programs/mosh.nix b/nixos/modules/programs/mosh.nix
index 359fe23e0ecdb..e08099e21a00c 100644
--- a/nixos/modules/programs/mosh.nix
+++ b/nixos/modules/programs/mosh.nix
@@ -33,7 +33,7 @@ in
     security.wrappers = mkIf cfg.withUtempter {
       utempter = {
         source = "${pkgs.libutempter}/lib/utempter/utempter";
-        owner = "nobody";
+        owner = "root";
         group = "utmp";
         setuid = false;
         setgid = true;
diff --git a/nixos/modules/programs/msmtp.nix b/nixos/modules/programs/msmtp.nix
index 217060e6b3b32..9c067bdc96957 100644
--- a/nixos/modules/programs/msmtp.nix
+++ b/nixos/modules/programs/msmtp.nix
@@ -78,6 +78,8 @@ in {
       source = "${pkgs.msmtp}/bin/sendmail";
       setuid = false;
       setgid = false;
+      owner = "root";
+      group = "root";
     };
 
     environment.etc."msmtprc".text = let
diff --git a/nixos/modules/programs/mtr.nix b/nixos/modules/programs/mtr.nix
index 75b710c1584fc..3cffe0fd8b2fa 100644
--- a/nixos/modules/programs/mtr.nix
+++ b/nixos/modules/programs/mtr.nix
@@ -20,6 +20,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.mtr;
+        defaultText = literalExpression "pkgs.mtr";
         description = ''
           The package to use.
         '';
@@ -31,8 +32,10 @@ in {
     environment.systemPackages = with pkgs; [ cfg.package ];
 
     security.wrappers.mtr-packet = {
-      source = "${cfg.package}/bin/mtr-packet";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_raw+p";
+      source = "${cfg.package}/bin/mtr-packet";
     };
   };
 }
diff --git a/nixos/modules/programs/neovim.nix b/nixos/modules/programs/neovim.nix
index 781c31d2b0ce7..4649662542dee 100644
--- a/nixos/modules/programs/neovim.nix
+++ b/nixos/modules/programs/neovim.nix
@@ -41,24 +41,36 @@ in {
     withRuby = mkOption {
       type = types.bool;
       default = true;
-      description = "Enable ruby provider.";
+      description = "Enable Ruby provider.";
+    };
+
+    withPython3 = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Enable Python 3 provider.";
+    };
+
+    withNodeJs = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable Node provider.";
     };
 
     configure = mkOption {
       type = types.attrs;
       default = {};
-      example = literalExample ''
-        configure = {
-            customRC = $''''
+      example = literalExpression ''
+        {
+          customRC = '''
             " here your custom configuration goes!
-            $'''';
-            packages.myVimPackage = with pkgs.vimPlugins; {
-              # loaded on launch
-              start = [ fugitive ];
-              # manually loadable by calling `:packadd $plugin-name`
-              opt = [ ];
-            };
+          ''';
+          packages.myVimPackage = with pkgs.vimPlugins; {
+            # loaded on launch
+            start = [ fugitive ];
+            # manually loadable by calling `:packadd $plugin-name`
+            opt = [ ];
           };
+        }
       '';
       description = ''
         Generate your init file from your list of plugins and custom commands.
@@ -69,7 +81,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.neovim-unwrapped;
-      defaultText = literalExample "pkgs.neovim-unwrapped";
+      defaultText = literalExpression "pkgs.neovim-unwrapped";
       description = "The package to use for the neovim binary.";
     };
 
@@ -82,8 +94,8 @@ in {
 
     runtime = mkOption {
       default = {};
-      example = literalExample ''
-        runtime."ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc";
+      example = literalExpression ''
+        { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
       '';
       description = ''
         Set of files that have to be linked in <filename>runtime</filename>.
@@ -139,10 +151,10 @@ in {
     environment.systemPackages = [
       cfg.finalPackage
     ];
-    environment.variables = { EDITOR = mkOverride 900 "nvim"; };
+    environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "nvim");
 
     programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package {
-      inherit (cfg) viAlias vimAlias;
+      inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby;
       configure = cfg.configure // {
 
         customRC = (cfg.configure.customRC or "") + ''
diff --git a/nixos/modules/programs/noisetorch.nix b/nixos/modules/programs/noisetorch.nix
index 5f3b0c8f5d1ee..f76555289f1a3 100644
--- a/nixos/modules/programs/noisetorch.nix
+++ b/nixos/modules/programs/noisetorch.nix
@@ -10,6 +10,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.noisetorch;
+      defaultText = literalExpression "pkgs.noisetorch";
       description = ''
         The noisetorch package to use.
       '';
@@ -18,8 +19,10 @@ in {
 
   config = mkIf cfg.enable {
     security.wrappers.noisetorch = {
-      source = "${cfg.package}/bin/noisetorch";
+      owner = "root";
+      group = "root";
       capabilities = "cap_sys_resource=+ep";
+      source = "${cfg.package}/bin/noisetorch";
     };
   };
 }
diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix
index f101a44587a16..d79c6c7340007 100644
--- a/nixos/modules/programs/npm.nix
+++ b/nixos/modules/programs/npm.nix
@@ -14,10 +14,11 @@ in
       enable = mkEnableOption "<command>npm</command> global config";
 
       package = mkOption {
-        type = types.path;
+        type = types.package;
         description = "The npm package version / flavor to use";
         default = pkgs.nodePackages.npm;
-        example = literalExample "pkgs.nodePackages_13_x.npm";
+        defaultText = literalExpression "pkgs.nodePackages.npm";
+        example = literalExpression "pkgs.nodePackages_13_x.npm";
       };
 
       npmrc = mkOption {
diff --git a/nixos/modules/programs/pantheon-tweaks.nix b/nixos/modules/programs/pantheon-tweaks.nix
new file mode 100644
index 0000000000000..0b8a19ea22c01
--- /dev/null
+++ b/nixos/modules/programs/pantheon-tweaks.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = teams.pantheon.members;
+  };
+
+  ###### interface
+  options = {
+    programs.pantheon-tweaks.enable = mkEnableOption "Pantheon Tweaks, an unofficial system settings panel for Pantheon";
+  };
+
+  ###### implementation
+  config = mkIf config.programs.pantheon-tweaks.enable {
+    services.xserver.desktopManager.pantheon.extraSwitchboardPlugs = [ pkgs.pantheon-tweaks ];
+  };
+}
diff --git a/nixos/modules/programs/plotinus.nix b/nixos/modules/programs/plotinus.nix
index e3549c79588b2..2c90a41ba0296 100644
--- a/nixos/modules/programs/plotinus.nix
+++ b/nixos/modules/programs/plotinus.nix
@@ -30,7 +30,7 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    environment.variables.XDG_DATA_DIRS = [ "${pkgs.plotinus}/share/gsettings-schemas/${pkgs.plotinus.name}" ];
+    environment.sessionVariables.XDG_DATA_DIRS = [ "${pkgs.plotinus}/share/gsettings-schemas/${pkgs.plotinus.name}" ];
     environment.variables.GTK3_MODULES = [ "${pkgs.plotinus}/lib/libplotinus.so" ];
   };
 }
diff --git a/nixos/modules/programs/proxychains.nix b/nixos/modules/programs/proxychains.nix
index 7743f79c1c0a9..3f44e23a93efe 100644
--- a/nixos/modules/programs/proxychains.nix
+++ b/nixos/modules/programs/proxychains.nix
@@ -120,7 +120,7 @@ in {
           Proxies to be used by proxychains.
         '';
 
-        example = literalExample ''
+        example = literalExpression ''
           { myproxy =
             { type = "socks4";
               host = "127.0.0.1";
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index 386ded9d98b61..963cd8853dbbb 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -43,6 +43,13 @@ let
 
     '';
 
+  mkSetuidRoot = source:
+    { setuid = true;
+      owner = "root";
+      group = "root";
+      inherit source;
+    };
+
 in
 
 {
@@ -59,7 +66,7 @@ in
         This must not be a store path, since the path is
         used outside the store (in particular in /etc/passwd).
       '';
-      example = literalExample "pkgs.zsh";
+      example = literalExpression "pkgs.zsh";
       type = types.either types.path types.shellPackage;
     };
 
@@ -109,14 +116,14 @@ in
       };
 
     security.wrappers = {
-      su.source        = "${pkgs.shadow.su}/bin/su";
-      sg.source        = "${pkgs.shadow.out}/bin/sg";
-      newgrp.source    = "${pkgs.shadow.out}/bin/newgrp";
-      newuidmap.source = "${pkgs.shadow.out}/bin/newuidmap";
-      newgidmap.source = "${pkgs.shadow.out}/bin/newgidmap";
+      su        = mkSetuidRoot "${pkgs.shadow.su}/bin/su";
+      sg        = mkSetuidRoot "${pkgs.shadow.out}/bin/sg";
+      newgrp    = mkSetuidRoot "${pkgs.shadow.out}/bin/newgrp";
+      newuidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newuidmap";
+      newgidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newgidmap";
     } // lib.optionalAttrs config.users.mutableUsers {
-      chsh.source      = "${pkgs.shadow.out}/bin/chsh";
-      passwd.source    = "${pkgs.shadow.out}/bin/passwd";
+      chsh   = mkSetuidRoot "${pkgs.shadow.out}/bin/chsh";
+      passwd = mkSetuidRoot "${pkgs.shadow.out}/bin/passwd";
     };
   };
 }
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index 6ac64a81fc244..db935abe4bb46 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -16,7 +16,12 @@ in {
 
   config = mkIf cfg.enable {
       environment.systemPackages = [ singularity ];
-      security.wrappers.singularity-suid.source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
+      security.wrappers.singularity-suid =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
+      };
       systemd.tmpfiles.rules = [
         "d /var/singularity/mnt/session 0770 root root -"
         "d /var/singularity/mnt/final 0770 root root -"
diff --git a/nixos/modules/programs/slock.nix b/nixos/modules/programs/slock.nix
index 0e1281e62cd77..ce80fcc5d4a8a 100644
--- a/nixos/modules/programs/slock.nix
+++ b/nixos/modules/programs/slock.nix
@@ -21,6 +21,11 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.slock ];
-    security.wrappers.slock.source = "${pkgs.slock.out}/bin/slock";
+    security.wrappers.slock =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.slock.out}/bin/slock";
+      };
   };
 }
diff --git a/nixos/modules/programs/spacefm.nix b/nixos/modules/programs/spacefm.nix
index 6d03608402fce..822fca3ecec77 100644
--- a/nixos/modules/programs/spacefm.nix
+++ b/nixos/modules/programs/spacefm.nix
@@ -29,11 +29,13 @@ in
           terminal_su = "${pkgs.sudo}/bin/sudo";
           graphical_su = "${pkgs.gksu}/bin/gksu";
         };
-        example = literalExample ''{
-          tmp_dir = "/tmp";
-          terminal_su = "''${pkgs.sudo}/bin/sudo";
-          graphical_su = "''${pkgs.gksu}/bin/gksu";
-        }'';
+        defaultText = literalExpression ''
+          {
+            tmp_dir = "/tmp";
+            terminal_su = "''${pkgs.sudo}/bin/sudo";
+            graphical_su = "''${pkgs.gksu}/bin/gksu";
+          }
+        '';
         description = ''
           The system-wide spacefm configuration.
           Parameters to be written to <filename>/etc/spacefm/spacefm.conf</filename>.
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 795f1a9f7b44c..5da15b68cf7d7 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -36,6 +36,7 @@ in
       askPassword = mkOption {
         type = types.str;
         default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass";
+        defaultText = literalExpression ''"''${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"'';
         description = "Program used by SSH to ask for passwords.";
       };
 
@@ -113,7 +114,7 @@ in
       agentPKCS11Whitelist = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "\${pkgs.opensc}/lib/opensc-pkcs11.so";
+        example = literalExpression ''"''${pkgs.opensc}/lib/opensc-pkcs11.so"'';
         description = ''
           A pattern-list of acceptable paths for PKCS#11 shared libraries
           that may be used with the -s option to ssh-add.
@@ -123,7 +124,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.openssh;
-        defaultText = "pkgs.openssh";
+        defaultText = literalExpression "pkgs.openssh";
         description = ''
           The package used for the openssh client and daemon.
         '';
@@ -180,7 +181,7 @@ in
         description = ''
           The set of system-wide known SSH hosts.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             myhost = {
               hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix
index 8b500f0383f4e..b454bf35229ee 100644
--- a/nixos/modules/programs/ssmtp.nix
+++ b/nixos/modules/programs/ssmtp.nix
@@ -54,7 +54,7 @@ in
           <citerefentry><refentrytitle>ssmtp</refentrytitle><manvolnum>5</manvolnum></citerefentry> configuration. Refer
           to <link xlink:href="https://linux.die.net/man/5/ssmtp.conf"/> for details on supported values.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             Debug = true;
             FromLineOverride = false;
@@ -181,6 +181,8 @@ in
       source = "${pkgs.ssmtp}/bin/sendmail";
       setuid = false;
       setgid = false;
+      owner = "root";
+      group = "root";
     };
 
   };
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index d5819a08e8f25..caf329c2536a3 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -92,10 +92,10 @@ in {
       default = with pkgs; [
         swaylock swayidle alacritty dmenu
       ];
-      defaultText = literalExample ''
+      defaultText = literalExpression ''
         with pkgs; [ swaylock swayidle alacritty dmenu ];
       '';
-      example = literalExample ''
+      example = literalExpression ''
         with pkgs; [
           i3status i3status-rust
           termite rofi light
diff --git a/nixos/modules/programs/traceroute.nix b/nixos/modules/programs/traceroute.nix
index 4eb0be3f0e0be..6e04057ac5034 100644
--- a/nixos/modules/programs/traceroute.nix
+++ b/nixos/modules/programs/traceroute.nix
@@ -19,8 +19,10 @@ in {
 
   config = mkIf cfg.enable {
     security.wrappers.traceroute = {
-      source = "${pkgs.traceroute}/bin/traceroute";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_raw+p";
+      source = "${pkgs.traceroute}/bin/traceroute";
     };
   };
 }
diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix
index 7ac4086d5f094..65d4db7834ffc 100644
--- a/nixos/modules/programs/tsm-client.nix
+++ b/nixos/modules/programs/tsm-client.nix
@@ -5,7 +5,7 @@ let
   inherit (builtins) length map;
   inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs;
   inherit (lib.modules) mkDefault mkIf;
-  inherit (lib.options) literalExample mkEnableOption mkOption;
+  inherit (lib.options) literalExpression mkEnableOption mkOption;
   inherit (lib.strings) concatStringsSep optionalString toLower;
   inherit (lib.types) addCheck attrsOf lines nullOr package path port str strMatching submodule;
 
@@ -123,7 +123,7 @@ let
     };
     options.text = mkOption {
       type = lines;
-      example = literalExample
+      example = literalExpression
         ''lib.modules.mkAfter "compression no"'';
       description = ''
         Additional text lines for the server stanza.
@@ -218,8 +218,8 @@ let
     package = mkOption {
       type = package;
       default = pkgs.tsm-client;
-      defaultText = "pkgs.tsm-client";
-      example = literalExample "pkgs.tsm-client-withGui";
+      defaultText = literalExpression "pkgs.tsm-client";
+      example = literalExpression "pkgs.tsm-client-withGui";
       description = ''
         The TSM client derivation to be
         added to the system environment.
diff --git a/nixos/modules/programs/udevil.nix b/nixos/modules/programs/udevil.nix
index ba5670f9dfe9d..0dc08c435df4a 100644
--- a/nixos/modules/programs/udevil.nix
+++ b/nixos/modules/programs/udevil.nix
@@ -9,6 +9,11 @@ in {
   options.programs.udevil.enable = mkEnableOption "udevil";
 
   config = mkIf cfg.enable {
-    security.wrappers.udevil.source = "${lib.getBin pkgs.udevil}/bin/udevil";
+    security.wrappers.udevil =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${lib.getBin pkgs.udevil}/bin/udevil";
+      };
   };
 }
diff --git a/nixos/modules/programs/vim.nix b/nixos/modules/programs/vim.nix
index 9f46dff2a2931..1695bc994732c 100644
--- a/nixos/modules/programs/vim.nix
+++ b/nixos/modules/programs/vim.nix
@@ -18,8 +18,8 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.vim;
-      defaultText = "pkgs.vim";
-      example = "pkgs.vimHugeX";
+      defaultText = literalExpression "pkgs.vim";
+      example = literalExpression "pkgs.vimHugeX";
       description = ''
         vim package to use.
       '';
diff --git a/nixos/modules/programs/wavemon.nix b/nixos/modules/programs/wavemon.nix
index ac665fe4a0236..e5ccacba75d4a 100644
--- a/nixos/modules/programs/wavemon.nix
+++ b/nixos/modules/programs/wavemon.nix
@@ -21,8 +21,10 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs; [ wavemon ];
     security.wrappers.wavemon = {
-      source = "${pkgs.wavemon}/bin/wavemon";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_admin+ep";
+      source = "${pkgs.wavemon}/bin/wavemon";
     };
   };
 }
diff --git a/nixos/modules/programs/weylus.nix b/nixos/modules/programs/weylus.nix
new file mode 100644
index 0000000000000..ea92c77e7c321
--- /dev/null
+++ b/nixos/modules/programs/weylus.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.weylus;
+in
+{
+  options.programs.weylus = with types; {
+    enable = mkEnableOption "weylus";
+
+    openFirewall = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        Open ports needed for the functionality of the program.
+      '';
+    };
+
+     users = mkOption {
+      type = listOf str;
+      default = [ ];
+      description = ''
+        To enable stylus and multi-touch support, the user you're going to use must be added to this list.
+        These users can synthesize input events system-wide, even when another user is logged in - untrusted users should not be added.
+      '';
+    };
+
+    package = mkOption {
+      type = package;
+      default = pkgs.weylus;
+      defaultText = "pkgs.weylus";
+      description = "Weylus package to install.";
+    };
+  };
+  config = mkIf cfg.enable {
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 1701 9001 ];
+    };
+
+    hardware.uinput.enable = true;
+
+    users.groups.uinput.members = cfg.users;
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/programs/wireshark.nix b/nixos/modules/programs/wireshark.nix
index 819f15b98a05a..f7b0727cb2b39 100644
--- a/nixos/modules/programs/wireshark.nix
+++ b/nixos/modules/programs/wireshark.nix
@@ -19,7 +19,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.wireshark-cli;
-        defaultText = "pkgs.wireshark-cli";
+        defaultText = literalExpression "pkgs.wireshark-cli";
         description = ''
           Which Wireshark package to install in the global environment.
         '';
diff --git a/nixos/modules/programs/wshowkeys.nix b/nixos/modules/programs/wshowkeys.nix
index 09b008af1d5db..f7b71d2bb0c89 100644
--- a/nixos/modules/programs/wshowkeys.nix
+++ b/nixos/modules/programs/wshowkeys.nix
@@ -17,6 +17,11 @@ in {
   };
 
   config = mkIf cfg.enable {
-    security.wrappers.wshowkeys.source = "${pkgs.wshowkeys}/bin/wshowkeys";
+    security.wrappers.wshowkeys =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.wshowkeys}/bin/wshowkeys";
+      };
   };
 }
diff --git a/nixos/modules/programs/xonsh.nix b/nixos/modules/programs/xonsh.nix
index c06fd1655c205..6e40db51cdb2f 100644
--- a/nixos/modules/programs/xonsh.nix
+++ b/nixos/modules/programs/xonsh.nix
@@ -27,7 +27,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.xonsh;
-        example = literalExample "pkgs.xonsh.override { configFile = \"/path/to/xonshrc\"; }";
+        defaultText = literalExpression "pkgs.xonsh";
+        example = literalExpression "pkgs.xonsh.override { configFile = \"/path/to/xonshrc\"; }";
         description = ''
           xonsh package to use.
         '';
diff --git a/nixos/modules/programs/xss-lock.nix b/nixos/modules/programs/xss-lock.nix
index ceb7259b3d779..aba76133e5e33 100644
--- a/nixos/modules/programs/xss-lock.nix
+++ b/nixos/modules/programs/xss-lock.nix
@@ -11,7 +11,8 @@ in
 
     lockerCommand = mkOption {
       default = "${pkgs.i3lock}/bin/i3lock";
-      example = literalExample "\${pkgs.i3lock-fancy}/bin/i3lock-fancy";
+      defaultText = literalExpression ''"''${pkgs.i3lock}/bin/i3lock"'';
+      example = literalExpression ''"''${pkgs.i3lock-fancy}/bin/i3lock-fancy"'';
       type = types.separatedString " ";
       description = "Locker to be used with xsslock";
     };
diff --git a/nixos/modules/programs/xwayland.nix b/nixos/modules/programs/xwayland.nix
index cb3c9c5b156c6..3a8080fa4c4d8 100644
--- a/nixos/modules/programs/xwayland.nix
+++ b/nixos/modules/programs/xwayland.nix
@@ -16,9 +16,8 @@ in
       type = types.str;
       default = optionalString config.fonts.fontDir.enable
         "/run/current-system/sw/share/X11/fonts";
-      defaultText = literalExample ''
-        optionalString config.fonts.fontDir.enable
-          "/run/current-system/sw/share/X11/fonts";
+      defaultText = literalExpression ''
+        optionalString config.fonts.fontDir.enable "/run/current-system/sw/share/X11/fonts"
       '';
       description = ''
         Default font path. Setting this option causes Xwayland to be rebuilt.
@@ -30,10 +29,10 @@ in
       default = pkgs.xwayland.override (oldArgs: {
         inherit (cfg) defaultFontPath;
       });
-      defaultText = literalExample ''
+      defaultText = literalExpression ''
         pkgs.xwayland.override (oldArgs: {
           inherit (config.programs.xwayland) defaultFontPath;
-        });
+        })
       '';
       description = "The Xwayland package to use.";
     };
diff --git a/nixos/modules/programs/yabar.nix b/nixos/modules/programs/yabar.nix
index 5de9331ac5204..a8fac41e899c1 100644
--- a/nixos/modules/programs/yabar.nix
+++ b/nixos/modules/programs/yabar.nix
@@ -45,7 +45,8 @@ in
 
       package = mkOption {
         default = pkgs.yabar-unstable;
-        example = literalExample "pkgs.yabar";
+        defaultText = literalExpression "pkgs.yabar-unstable";
+        example = literalExpression "pkgs.yabar";
         type = types.package;
 
         # `yabar-stable` segfaults under certain conditions.
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix
index f24842a479193..9d7622bd32873 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.nix
+++ b/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -48,7 +48,7 @@ in
 
         package = mkOption {
           default = pkgs.oh-my-zsh;
-          defaultText = "pkgs.oh-my-zsh";
+          defaultText = literalExpression "pkgs.oh-my-zsh";
           description = ''
             Package to install for `oh-my-zsh` usage.
           '';
diff --git a/nixos/modules/programs/zsh/zsh-autoenv.nix b/nixos/modules/programs/zsh/zsh-autoenv.nix
index 630114bcda9f3..62f497a45dd08 100644
--- a/nixos/modules/programs/zsh/zsh-autoenv.nix
+++ b/nixos/modules/programs/zsh/zsh-autoenv.nix
@@ -10,7 +10,7 @@ in {
       enable = mkEnableOption "zsh-autoenv";
       package = mkOption {
         default = pkgs.zsh-autoenv;
-        defaultText = "pkgs.zsh-autoenv";
+        defaultText = literalExpression "pkgs.zsh-autoenv";
         description = ''
           Package to install for `zsh-autoenv` usage.
         '';
diff --git a/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
index 037888fdc5a84..a8fcfff95e594 100644
--- a/nixos/modules/programs/zsh/zsh-autosuggestions.nix
+++ b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -40,7 +40,7 @@ in
       type = with types; attrsOf str;
       default = {};
       description = "Attribute set with additional configuration values";
-      example = literalExample ''
+      example = literalExpression ''
         {
           "ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" = "20";
         }
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index 927a904369d5e..1eb53ccae52be 100644
--- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -42,7 +42,7 @@ in
         default = {};
         type = types.attrsOf types.str;
 
-        example = literalExample ''
+        example = literalExpression ''
           {
             "rm -rf *" = "fg=white,bold,bg=red";
           }
@@ -59,7 +59,7 @@ in
         default = {};
         type = types.attrsOf types.str;
 
-        example = literalExample ''
+        example = literalExpression ''
           {
             "alias" = "fg=magenta,bold";
           }
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index dc6c958ca88b2..e5c5b08f8d4da 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -283,21 +283,8 @@ in
     # see https://github.com/NixOS/nixpkgs/issues/132732
     environment.etc.zinputrc.text = builtins.readFile ./zinputrc;
 
-    environment.systemPackages =
-      let
-        completions =
-          if lib.versionAtLeast (lib.getVersion config.nix.package) "2.4pre"
-          then
-            pkgs.nix-zsh-completions.overrideAttrs
-              (_: {
-                postInstall = ''
-                  rm $out/share/zsh/site-functions/_nix
-                '';
-              })
-          else pkgs.nix-zsh-completions;
-      in
-      [ pkgs.zsh ]
-      ++ optional cfg.enableCompletion completions;
+    environment.systemPackages = [ pkgs.zsh ]
+      ++ optional cfg.enableCompletion pkgs.nix-zsh-completions;
 
     environment.pathsToLink = optional cfg.enableCompletion "/share/zsh";
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 233e3ee848be8..8e1d6f7bc4a57 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -27,6 +27,7 @@ with lib;
     (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 [ "networking" "wicd" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "environment" "blcr" "enable" ] "The BLCR module has been removed")
     (mkRemovedOptionModule [ "services" "beegfsEnable" ] "The BeeGFS module has been removed")
     (mkRemovedOptionModule [ "services" "beegfs" ] "The BeeGFS module has been removed")
@@ -78,6 +79,9 @@ with lib;
         The hidepid module was removed, since the underlying machinery
         is broken when using cgroups-v2.
     '')
+    (mkRemovedOptionModule ["services" "wakeonlan"] "This module was removed in favor of enabling it with networking.interfaces.<name>.wakeOnLan")
+
+    (mkRemovedOptionModule [ "services" "kippo" ] "The corresponding package was removed from nixpkgs.")
 
     # Do NOT add any option renames here, see top of the file
   ];
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index bcbd17d8e10e4..cfbc8e91903ef 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -192,6 +192,14 @@ let
       ++ data.extraLegoRenewFlags
     );
 
+    # We need to collect all the ACME webroots to grant them write
+    # access in the systemd service.
+    webroots =
+      lib.remove null
+        (lib.unique
+            (builtins.map
+            (certAttrs: certAttrs.webroot)
+            (lib.attrValues config.security.acme.certs)));
   in {
     inherit accountHash cert selfsignedDeps;
 
@@ -288,6 +296,8 @@ let
           "acme/.lego/accounts/${accountHash}"
         ];
 
+        ReadWritePaths = commonServiceConfig.ReadWritePaths ++ webroots;
+
         # Needs to be space separated, but can't use a multiline string because that'll include newlines
         BindPaths = [
           "${accountDir}:/tmp/accounts"
@@ -486,7 +496,7 @@ let
       extraDomainNames = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [
             "example.org"
             "mydomain.org"
@@ -656,7 +666,7 @@ in {
           to those units if they rely on the certificates being present,
           or trigger restarts of the service if certificates get renewed.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             "example.com" = {
               webroot = "/var/lib/acme/acme-challenge/";
diff --git a/nixos/modules/security/ca.nix b/nixos/modules/security/ca.nix
index 7df86e71423f3..f71d9d90ec5b8 100644
--- a/nixos/modules/security/ca.nix
+++ b/nixos/modules/security/ca.nix
@@ -8,12 +8,10 @@ let
 
   cacertPackage = pkgs.cacert.override {
     blacklist = cfg.caCertificateBlacklist;
+    extraCertificateFiles = cfg.certificateFiles;
+    extraCertificateStrings = cfg.certificates;
   };
-
-  caCertificates = pkgs.runCommand "ca-certificates.crt" {
-    files = cfg.certificateFiles ++ [ (builtins.toFile "extra.crt" (concatStringsSep "\n" cfg.certificates)) ];
-    preferLocalBuild = true;
-  } "awk 1 $files > $out";  # awk ensures a newline between each pair of consecutive files
+  caBundle = "${cacertPackage}/etc/ssl/certs/ca-bundle.crt";
 
 in
 
@@ -24,7 +22,7 @@ in
     security.pki.certificateFiles = mkOption {
       type = types.listOf types.path;
       default = [];
-      example = literalExample "[ \"\${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt\" ]";
+      example = literalExpression ''[ "''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]'';
       description = ''
         A list of files containing trusted root certificates in PEM
         format. These are concatenated to form
@@ -37,7 +35,7 @@ in
     security.pki.certificates = mkOption {
       type = types.listOf types.str;
       default = [];
-      example = literalExample ''
+      example = literalExpression ''
         [ '''
             NixOS.org
             =========
@@ -74,16 +72,17 @@ in
 
   config = {
 
-    security.pki.certificateFiles = [ "${cacertPackage}/etc/ssl/certs/ca-bundle.crt" ];
-
     # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility.
-    environment.etc."ssl/certs/ca-certificates.crt".source = caCertificates;
+    environment.etc."ssl/certs/ca-certificates.crt".source = caBundle;
 
     # Old NixOS compatibility.
-    environment.etc."ssl/certs/ca-bundle.crt".source = caCertificates;
+    environment.etc."ssl/certs/ca-bundle.crt".source = caBundle;
 
     # CentOS/Fedora compatibility.
-    environment.etc."pki/tls/certs/ca-bundle.crt".source = caCertificates;
+    environment.etc."pki/tls/certs/ca-bundle.crt".source = caBundle;
+
+    # P11-Kit trust source.
+    environment.etc."ssl/trust-source".source = "${cacertPackage.p11kit}/etc/ssl/trust-source";
 
   };
 
diff --git a/nixos/modules/security/chromium-suid-sandbox.nix b/nixos/modules/security/chromium-suid-sandbox.nix
index b83dbc4202a8d..bb99c053f7185 100644
--- a/nixos/modules/security/chromium-suid-sandbox.nix
+++ b/nixos/modules/security/chromium-suid-sandbox.nix
@@ -28,6 +28,11 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ sandbox ];
-    security.wrappers.${sandbox.passthru.sandboxExecutableName}.source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
+    security.wrappers.${sandbox.passthru.sandboxExecutableName} =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
+      };
   };
 }
diff --git a/nixos/modules/security/dhparams.nix b/nixos/modules/security/dhparams.nix
index 62a499ea624de..012be2887d898 100644
--- a/nixos/modules/security/dhparams.nix
+++ b/nixos/modules/security/dhparams.nix
@@ -53,7 +53,7 @@ in {
           coerce = bits: { inherit bits; };
         in attrsOf (coercedTo int coerce (submodule paramsSubmodule));
         default = {};
-        example = lib.literalExample "{ nginx.bits = 3072; }";
+        example = lib.literalExpression "{ nginx.bits = 3072; }";
         description = ''
           Diffie-Hellman parameters to generate.
 
diff --git a/nixos/modules/security/doas.nix b/nixos/modules/security/doas.nix
index 27f6870aaf374..2a814f17e454f 100644
--- a/nixos/modules/security/doas.nix
+++ b/nixos/modules/security/doas.nix
@@ -15,7 +15,7 @@ let
     (optionalString rule.noLog "nolog")
     (optionalString rule.persist "persist")
     (optionalString rule.keepEnv "keepenv")
-    "setenv { SSH_AUTH_SOCK ${concatStringsSep " " rule.setEnv} }"
+    "setenv { SSH_AUTH_SOCK TERMINFO TERMINFO_DIRS ${concatStringsSep " " rule.setEnv} }"
   ];
 
   mkArgs = rule:
@@ -77,7 +77,7 @@ in
         You can use <code>mkBefore</code> and/or <code>mkAfter</code> to ensure
         this is the case when configuration options are merged.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         [
           # Allow execution of any command by any user in group doas, requiring
           # a password and keeping any previously-defined environment variables.
@@ -241,9 +241,12 @@ in
       }
     ];
 
-    security.wrappers = {
-      doas.source = "${doas}/bin/doas";
-    };
+    security.wrappers.doas =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${doas}/bin/doas";
+      };
 
     environment.systemPackages = [
       doas
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index c47be80b9dc3c..bbe246fe229ec 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -186,7 +186,12 @@ in
   config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
     environment.systemPackages = [ pkgs.duo-unix ];
 
-    security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo";
+    security.wrappers.login_duo =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.duo-unix.out}/bin/login_duo";
+      };
 
     system.activationScripts = {
       login_duo = mkIf cfg.ssh.enable ''
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index fc9e7939d8148..065587bc286e6 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, pkgs, lib, ... }:
 
 with lib;
 
@@ -13,7 +13,7 @@ with lib;
       default = false;
       description = ''
         Disable kernel module loading once the system is fully initialised.
-        Module loading is disabled until the next reboot.  Problems caused
+        Module loading is disabled until the next reboot. Problems caused
         by delayed module loading can be fixed by adding the module(s) in
         question to <option>boot.kernelModules</option>.
       '';
@@ -29,20 +29,30 @@ with lib;
             else [ x.fsType ]
         else []) config.system.build.fileSystems;
 
-    systemd.services.disable-kernel-module-loading = rec {
+    systemd.services.disable-kernel-module-loading = {
       description = "Disable kernel module loading";
 
+      wants = [ "systemd-udevd.service" ];
       wantedBy = [ config.systemd.defaultUnit ];
 
-      after = [ "systemd-udev-settle.service" "firewall.service" "systemd-modules-load.service" ] ++ wantedBy;
+      after =
+        [ "firewall.service"
+          "systemd-modules-load.service"
+           config.systemd.defaultUnit
+        ];
 
       unitConfig.ConditionPathIsReadWrite = "/proc/sys/kernel";
 
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        ExecStart = "/bin/sh -c 'echo -n 1 >/proc/sys/kernel/modules_disabled'";
-      };
+      serviceConfig =
+        { Type = "oneshot";
+          RemainAfterExit = true;
+          TimeoutSec = 180;
+        };
+
+      script = ''
+        ${pkgs.udev}/bin/udevadm settle
+        echo -n 1 >/proc/sys/kernel/modules_disabled
+      '';
     };
   };
 }
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 163d75d7caf29..70bce783a90b6 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -197,6 +197,46 @@ let
         '';
       };
 
+      ttyAudit = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable or disable TTY auditing for specified users
+          '';
+        };
+
+        enablePattern = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            For each user matching one of comma-separated
+            glob patterns, enable TTY auditing
+          '';
+        };
+
+        disablePattern = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            For each user matching one of comma-separated
+            glob patterns, disable TTY auditing
+          '';
+        };
+
+        openOnly = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Set the TTY audit flag when opening the session,
+            but do not restore it when closing the session.
+            Using this option is necessary for some services
+            that don't fork() to run the authenticated session,
+            such as sudo.
+          '';
+        };
+      };
+
       forwardXAuth = mkOption {
         default = false;
         type = types.bool;
@@ -370,46 +410,64 @@ let
       # Samba stuff to the Samba module.  This requires that the PAM
       # module provides the right hooks.
       text = mkDefault
-        (''
-          # Account management.
-          account required pam_unix.so
-          ${optionalString use_ldap
-              "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
-          ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false)
-              "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"}
-          ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess)
-              "account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so"}
-          ${optionalString config.krb5.enable
-              "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"}
-          ${optionalString cfg.googleOsLoginAccountVerification ''
+        (
+          ''
+            # Account management.
+            account required pam_unix.so
+          '' +
+          optionalString use_ldap ''
+            account sufficient ${pam_ldap}/lib/security/pam_ldap.so
+          '' +
+          optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) ''
+            account sufficient ${pkgs.sssd}/lib/security/pam_sss.so
+          '' +
+          optionalString (config.services.sssd.enable && cfg.sssdStrictAccess) ''
+            account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so
+          '' +
+          optionalString config.krb5.enable ''
+            account sufficient ${pam_krb5}/lib/security/pam_krb5.so
+          '' +
+          optionalString cfg.googleOsLoginAccountVerification ''
             account [success=ok ignore=ignore default=die] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so
             account [success=ok default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so
-          ''}
-
-          # Authentication management.
-          ${optionalString cfg.googleOsLoginAuthentication
-              "auth [success=done perm_denied=bad default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so"}
-          ${optionalString cfg.rootOK
-              "auth sufficient pam_rootok.so"}
-          ${optionalString cfg.requireWheel
-              "auth required pam_wheel.so use_uid"}
-          ${optionalString cfg.logFailures
-              "auth required pam_faillock.so"}
-          ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth)
-              "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}"}
-          ${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 (u2f.appId != null) "appid=${u2f.appId}"}"}
-          ${optionalString cfg.usbAuth
-              "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"}
-          ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
-              "auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
-          ${let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth
-              "auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.challengeResponsePath != null) "chalresp_path=${yubi.challengeResponsePath}"} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}"}
-          ${optionalString cfg.fprintAuth
-              "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"}
-        '' +
+          '' +
+          ''
+
+            # Authentication management.
+          '' +
+          optionalString cfg.googleOsLoginAuthentication ''
+            auth [success=done perm_denied=bad default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so
+          '' +
+          optionalString cfg.rootOK ''
+            auth sufficient pam_rootok.so
+          '' +
+          optionalString cfg.requireWheel ''
+            auth required pam_wheel.so use_uid
+          '' +
+          optionalString cfg.logFailures ''
+            auth required pam_faillock.so
+          '' +
+          optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) ''
+            auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}
+          '' +
+          (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 (u2f.appId != null) "appid=${u2f.appId}"}
+          '') +
+          optionalString cfg.usbAuth ''
+            auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
+          '' +
+          (let oath = config.security.pam.oath; in optionalString cfg.oathAuth ''
+            auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
+          '') +
+          (let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth ''
+            auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.challengeResponsePath != null) "chalresp_path=${yubi.challengeResponsePath}"} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}
+          '') +
+          optionalString cfg.fprintAuth ''
+            auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so
+          '' +
           # Modules in this block require having the password set in PAM_AUTHTOK.
           # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
           # after it succeeds. Certain modules need to run after pam_unix
@@ -417,109 +475,151 @@ let
           # earlier point and it will run again with 'sufficient' further down.
           # We use try_first_pass the second time to avoid prompting password twice
           (optionalString (cfg.unixAuth &&
-          (config.security.pam.enableEcryptfs
-            || cfg.pamMount
-            || cfg.enableKwallet
-            || cfg.enableGnomeKeyring
-            || cfg.googleAuthenticator.enable
-            || cfg.gnupg.enable
-            || cfg.duoSecurity.enable)) ''
-              auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
-              ${optionalString config.security.pam.enableEcryptfs
-                "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
-              ${optionalString cfg.pamMount
-                "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
-              ${optionalString cfg.enableKwallet
-                ("auth optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so" +
-                 " kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5")}
-              ${optionalString cfg.enableGnomeKeyring
-                "auth optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"}
-              ${optionalString cfg.gnupg.enable
-                "auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"
-                + optionalString cfg.gnupg.storeOnly " store-only"
-               }
-              ${optionalString cfg.googleAuthenticator.enable
-                "auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp"}
-              ${optionalString cfg.duoSecurity.enable
-                "auth required ${pkgs.duo-unix}/lib/security/pam_duo.so"}
-            '') + ''
-          ${optionalString cfg.unixAuth
-              "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass"}
-          ${optionalString cfg.otpwAuth
-              "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
-          ${optionalString use_ldap
-              "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"}
-          ${optionalString config.services.sssd.enable
-              "auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass"}
-          ${optionalString config.krb5.enable ''
+            (config.security.pam.enableEcryptfs
+              || cfg.pamMount
+              || cfg.enableKwallet
+              || cfg.enableGnomeKeyring
+              || cfg.googleAuthenticator.enable
+              || cfg.gnupg.enable
+              || cfg.duoSecurity.enable))
+            (
+              ''
+                auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
+              '' +
+              optionalString config.security.pam.enableEcryptfs ''
+                auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap
+              '' +
+              optionalString cfg.pamMount ''
+                auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
+              '' +
+              optionalString cfg.enableKwallet ''
+               auth optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
+              '' +
+              optionalString cfg.enableGnomeKeyring ''
+                auth optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so
+              '' +
+              optionalString cfg.gnupg.enable ''
+                auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.storeOnly " store-only"}
+              '' +
+              optionalString cfg.googleAuthenticator.enable ''
+                auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp
+              '' +
+              optionalString cfg.duoSecurity.enable ''
+                auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
+              ''
+            )) +
+          optionalString cfg.unixAuth ''
+            auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
+          '' +
+          optionalString cfg.otpwAuth ''
+            auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so
+          '' +
+          optionalString use_ldap ''
+            auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass
+          '' +
+          optionalString config.services.sssd.enable ''
+            auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass
+          '' +
+          optionalString config.krb5.enable ''
             auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
             auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
             auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
-          ''}
-          auth required pam_deny.so
-
-          # Password management.
-          password sufficient pam_unix.so nullok sha512
-          ${optionalString config.security.pam.enableEcryptfs
-              "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
-          ${optionalString cfg.pamMount
-              "password optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
-          ${optionalString use_ldap
-              "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
-          ${optionalString config.services.sssd.enable
-              "password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok"}
-          ${optionalString config.krb5.enable
-              "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"}
-          ${optionalString cfg.enableGnomeKeyring
-              "password optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok"}
-
-          # Session management.
-          ${optionalString cfg.setEnvironment ''
+          '' +
+          ''
+            auth required pam_deny.so
+
+            # Password management.
+            password sufficient pam_unix.so nullok sha512
+          '' +
+          optionalString config.security.pam.enableEcryptfs ''
+            password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
+          '' +
+          optionalString cfg.pamMount ''
+            password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
+          '' +
+          optionalString use_ldap ''
+            password sufficient ${pam_ldap}/lib/security/pam_ldap.so
+          '' +
+          optionalString config.services.sssd.enable ''
+            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok
+          '' +
+          optionalString config.krb5.enable ''
+            password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
+          '' +
+          optionalString cfg.enableGnomeKeyring ''
+            password optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok
+          '' +
+          ''
+
+            # Session management.
+          '' +
+          optionalString cfg.setEnvironment ''
             session required pam_env.so conffile=/etc/pam/environment readenv=0
-          ''}
-          session required pam_unix.so
-          ${optionalString cfg.setLoginUid
-              "session ${
-                if config.boot.isContainer then "optional" else "required"
-              } pam_loginuid.so"}
-          ${optionalString cfg.makeHomeDir
-              "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0022"}
-          ${optionalString cfg.updateWtmp
-              "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"}
-          ${optionalString config.security.pam.enableEcryptfs
-              "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
-          ${optionalString cfg.pamMount
-              "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
-          ${optionalString use_ldap
-              "session optional ${pam_ldap}/lib/security/pam_ldap.so"}
-          ${optionalString config.services.sssd.enable
-              "session optional ${pkgs.sssd}/lib/security/pam_sss.so"}
-          ${optionalString config.krb5.enable
-              "session optional ${pam_krb5}/lib/security/pam_krb5.so"}
-          ${optionalString cfg.otpwAuth
-              "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"}
-          ${optionalString cfg.startSession
-              "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"}
-          ${optionalString cfg.forwardXAuth
-              "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"}
-          ${optionalString (cfg.limits != [])
-              "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"}
-          ${optionalString (cfg.showMotd && config.users.motd != null)
-              "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"}
-          ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable)
-              "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"}
-          ${optionalString (cfg.enableKwallet)
-              ("session optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so" +
-               " kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5")}
-          ${optionalString (cfg.enableGnomeKeyring)
-              "session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start"}
-          ${optionalString cfg.gnupg.enable
-              "session optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"
-              + optionalString cfg.gnupg.noAutostart " no-autostart"
-           }
-          ${optionalString (config.virtualisation.lxc.lxcfs.enable)
-               "session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all"}
-        '');
+          '' +
+          ''
+            session required pam_unix.so
+          '' +
+          optionalString cfg.setLoginUid ''
+            session ${if config.boot.isContainer then "optional" else "required"} pam_loginuid.so
+          '' +
+          optionalString cfg.ttyAudit.enable ''
+            session required ${pkgs.pam}/lib/security/pam_tty_audit.so
+                open_only=${toString cfg.ttyAudit.openOnly}
+                ${optionalString (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"}
+                ${optionalString (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"}
+          '' +
+          optionalString cfg.makeHomeDir ''
+            session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
+          '' +
+          optionalString cfg.updateWtmp ''
+            session required ${pkgs.pam}/lib/security/pam_lastlog.so silent
+          '' +
+          optionalString config.security.pam.enableEcryptfs ''
+            session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
+          '' +
+          optionalString cfg.pamMount ''
+            session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
+          '' +
+          optionalString use_ldap ''
+            session optional ${pam_ldap}/lib/security/pam_ldap.so
+          '' +
+          optionalString config.services.sssd.enable ''
+            session optional ${pkgs.sssd}/lib/security/pam_sss.so
+          '' +
+          optionalString config.krb5.enable ''
+            session optional ${pam_krb5}/lib/security/pam_krb5.so
+          '' +
+          optionalString cfg.otpwAuth ''
+            session optional ${pkgs.otpw}/lib/security/pam_otpw.so
+          '' +
+          optionalString cfg.startSession ''
+            session optional ${pkgs.systemd}/lib/security/pam_systemd.so
+          '' +
+          optionalString cfg.forwardXAuth ''
+            session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99
+          '' +
+          optionalString (cfg.limits != []) ''
+            session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}
+          '' +
+          optionalString (cfg.showMotd && config.users.motd != null) ''
+            session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}
+          '' +
+          optionalString (cfg.enableAppArmor && config.security.apparmor.enable) ''
+            session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug
+          '' +
+          optionalString (cfg.enableKwallet) ''
+            session optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
+          '' +
+          optionalString (cfg.enableGnomeKeyring) ''
+            session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start
+          '' +
+          optionalString cfg.gnupg.enable ''
+            session optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.noAutostart " no-autostart"}
+          '' +
+          optionalString (config.virtualisation.lxc.lxcfs.enable) ''
+            session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all
+          ''
+        );
     };
 
   };
@@ -586,7 +686,7 @@ in
     };
 
     security.pam.services = mkOption {
-      default = [];
+      default = {};
       type = with types; attrsOf (submodule pamOpts);
       description =
         ''
@@ -869,9 +969,10 @@ in
 
     security.wrappers = {
       unix_chkpwd = {
-        source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
-        owner = "root";
         setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
       };
     };
 
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index e25ace38f57f9..462b7f89e2f48 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -33,7 +33,7 @@ in
       additionalSearchPaths = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.bindfs ]";
+        example = literalExpression "[ pkgs.bindfs ]";
         description = ''
           Additional programs to include in the search path of pam_mount.
           Useful for example if you want to use some FUSE filesystems like bindfs.
@@ -43,7 +43,7 @@ in
       fuseMountOptions = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [ "nodev" "nosuid" "force-user=%(USER)" "gid=%(USERGID)" "perms=0700" "chmod-deny" "chown-deny" "chgrp-deny" ]
         '';
         description = ''
diff --git a/nixos/modules/security/pam_usb.nix b/nixos/modules/security/pam_usb.nix
index c695ba075ca94..51d81e823f867 100644
--- a/nixos/modules/security/pam_usb.nix
+++ b/nixos/modules/security/pam_usb.nix
@@ -32,8 +32,18 @@ in
 
     # Make sure pmount and pumount are setuid wrapped.
     security.wrappers = {
-      pmount.source = "${pkgs.pmount.out}/bin/pmount";
-      pumount.source = "${pkgs.pmount.out}/bin/pumount";
+      pmount =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.pmount.out}/bin/pmount";
+        };
+      pumount =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.pmount.out}/bin/pumount";
+        };
     };
 
     environment.systemPackages = [ pkgs.pmount ];
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index f556cca23cdcf..d9c58152f1faa 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -83,8 +83,18 @@ in
     security.pam.services.polkit-1 = {};
 
     security.wrappers = {
-      pkexec.source = "${pkgs.polkit.bin}/bin/pkexec";
-      polkit-agent-helper-1.source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
+      pkexec =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.polkit.bin}/bin/pkexec";
+        };
+      polkit-agent-helper-1 =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
+        };
     };
 
     systemd.tmpfiles.rules = [
diff --git a/nixos/modules/security/rtkit.nix b/nixos/modules/security/rtkit.nix
index a7b27cbcf215d..ad8746808e85e 100644
--- a/nixos/modules/security/rtkit.nix
+++ b/nixos/modules/security/rtkit.nix
@@ -35,9 +35,12 @@ with lib;
     services.dbus.packages = [ pkgs.rtkit ];
 
     users.users.rtkit =
-      { uid = config.ids.uids.rtkit;
+      {
+        isSystemUser = true;
+        group = "rtkit";
         description = "RealtimeKit daemon";
       };
+    users.groups.rtkit = {};
 
   };
 
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 2e73f8f4f311d..99e578f8adae6 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -45,7 +45,7 @@ in
     security.sudo.package = mkOption {
       type = types.package;
       default = pkgs.sudo;
-      defaultText = "pkgs.sudo";
+      defaultText = literalExpression "pkgs.sudo";
       description = ''
         Which package to use for `sudo`.
       '';
@@ -91,7 +91,7 @@ in
         this is the case when configuration options are merged.
       '';
       default = [];
-      example = literalExample ''
+      example = literalExpression ''
         [
           # Allow execution of any command by all users in group sudo,
           # requiring a password.
diff --git a/nixos/modules/security/systemd-confinement.nix b/nixos/modules/security/systemd-confinement.nix
index 0a09a755e93c1..d859c45c74f7a 100644
--- a/nixos/modules/security/systemd-confinement.nix
+++ b/nixos/modules/security/systemd-confinement.nix
@@ -62,8 +62,8 @@ in {
       options.confinement.binSh = lib.mkOption {
         type = types.nullOr types.path;
         default = toplevelConfig.environment.binsh;
-        defaultText = "config.environment.binsh";
-        example = lib.literalExample "\${pkgs.dash}/bin/dash";
+        defaultText = lib.literalExpression "config.environment.binsh";
+        example = lib.literalExpression ''"''${pkgs.dash}/bin/dash"'';
         description = ''
           The program to make available as <filename>/bin/sh</filename> inside
           the chroot. If this is set to <literal>null</literal>, no
diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix
index 27f9b58c9755a..be85fd246e3cc 100644
--- a/nixos/modules/security/tpm2.nix
+++ b/nixos/modules/security/tpm2.nix
@@ -26,8 +26,7 @@ in {
       '';
       type = lib.types.nullOr lib.types.str;
       default = if cfg.abrmd.enable then "tss" else "root";
-      defaultText = ''"tss" when using the userspace resource manager,'' +
-                    ''"root" otherwise'';
+      defaultText = lib.literalExpression ''if config.security.tpm2.abrmd.enable then "tss" else "root"'';
     };
 
     tssGroup = lib.mkOption {
@@ -57,7 +56,7 @@ in {
         description = "tpm2-abrmd package to use";
         type = lib.types.package;
         default = pkgs.tpm2-abrmd;
-        defaultText = "pkgs.tpm2-abrmd";
+        defaultText = lib.literalExpression "pkgs.tpm2-abrmd";
       };
     };
 
@@ -71,7 +70,7 @@ in {
         description = "tpm2-pkcs11 package to use";
         type = lib.types.package;
         default = pkgs.tpm2-pkcs11;
-        defaultText = "pkgs.tpm2-pkcs11";
+        defaultText = lib.literalExpression "pkgs.tpm2-pkcs11";
       };
     };
 
@@ -146,6 +145,7 @@ in {
       # Create the tss user and group only if the default value is used
       users.users.${cfg.tssUser} = lib.mkIf (cfg.tssUser == "tss") {
         isSystemUser = true;
+        group = "tss";
       };
       users.groups.${cfg.tssGroup} = lib.mkIf (cfg.tssGroup == "tss") {};
 
@@ -172,7 +172,7 @@ in {
           BusName = "com.intel.tss2.Tabrmd";
           ExecStart = "${cfg.abrmd.package}/bin/tpm2-abrmd";
           User = "tss";
-          Group = "nogroup";
+          Group = "tss";
         };
       };
 
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 1e65f45151555..a47de7e04f7af 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -5,85 +5,140 @@ let
 
   parentWrapperDir = dirOf wrapperDir;
 
-  programs =
-    (lib.mapAttrsToList
-      (n: v: (if v ? program then v else v // {program=n;}))
-      wrappers);
-
   securityWrapper = pkgs.callPackage ./wrapper.nix {
     inherit parentWrapperDir;
   };
 
+  fileModeType =
+    let
+      # taken from the chmod(1) man page
+      symbolic = "[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+";
+      numeric = "[-+=]?[0-7]{0,4}";
+      mode = "((${symbolic})(,${symbolic})*)|(${numeric})";
+    in
+     lib.types.strMatching mode
+     // { description = "file mode string"; };
+
+  wrapperType = lib.types.submodule ({ name, config, ... }: {
+    options.source = lib.mkOption
+      { type = lib.types.path;
+        description = "The absolute path to the program to be wrapped.";
+      };
+    options.program = lib.mkOption
+      { type = with lib.types; nullOr str;
+        default = name;
+        description = ''
+          The name of the wrapper program. Defaults to the attribute name.
+        '';
+      };
+    options.owner = lib.mkOption
+      { type = lib.types.str;
+        description = "The owner of the wrapper program.";
+      };
+    options.group = lib.mkOption
+      { type = lib.types.str;
+        description = "The group of the wrapper program.";
+      };
+    options.permissions = lib.mkOption
+      { type = fileModeType;
+        default  = "u+rx,g+x,o+x";
+        example = "a+rx";
+        description = ''
+          The permissions of the wrapper program. The format is that of a
+          symbolic or numeric file mode understood by <command>chmod</command>.
+        '';
+      };
+    options.capabilities = lib.mkOption
+      { type = lib.types.commas;
+        default = "";
+        description = ''
+          A comma-separated list of capabilities to be given to the wrapper
+          program. For capabilities supported by the system check the
+          <citerefentry>
+            <refentrytitle>capabilities</refentrytitle>
+            <manvolnum>7</manvolnum>
+          </citerefentry>
+          manual page.
+
+          <note><para>
+            <literal>cap_setpcap</literal>, which is required for the wrapper
+            program to be able to raise caps into the Ambient set is NOT raised
+            to the Ambient set so that the real program cannot modify its own
+            capabilities!! This may be too restrictive for cases in which the
+            real program needs cap_setpcap but it at least leans on the side
+            security paranoid vs. too relaxed.
+          </para></note>
+        '';
+      };
+    options.setuid = lib.mkOption
+      { type = lib.types.bool;
+        default = false;
+        description = "Whether to add the setuid bit the wrapper program.";
+      };
+    options.setgid = lib.mkOption
+      { type = lib.types.bool;
+        default = false;
+        description = "Whether to add the setgid bit the wrapper program.";
+      };
+  });
+
   ###### Activation script for the setcap wrappers
   mkSetcapProgram =
     { program
     , capabilities
     , source
-    , owner  ? "nobody"
-    , group  ? "nogroup"
-    , permissions ? "u+rx,g+x,o+x"
+    , owner
+    , group
+    , permissions
     , ...
     }:
     assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
     ''
-      cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
-      echo -n "${source}" > $wrapperDir/${program}.real
+      cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
+      echo -n "${source}" > "$wrapperDir/${program}.real"
 
       # Prevent races
-      chmod 0000 $wrapperDir/${program}
-      chown ${owner}.${group} $wrapperDir/${program}
+      chmod 0000 "$wrapperDir/${program}"
+      chown ${owner}.${group} "$wrapperDir/${program}"
 
       # Set desired capabilities on the file plus cap_setpcap so
       # the wrapper program can elevate the capabilities set on
       # its file into the Ambient set.
-      ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program}
+      ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" "$wrapperDir/${program}"
 
       # Set the executable bit
-      chmod ${permissions} $wrapperDir/${program}
+      chmod ${permissions} "$wrapperDir/${program}"
     '';
 
   ###### Activation script for the setuid wrappers
   mkSetuidProgram =
     { program
     , source
-    , owner  ? "nobody"
-    , group  ? "nogroup"
-    , setuid ? false
-    , setgid ? false
-    , permissions ? "u+rx,g+x,o+x"
+    , owner
+    , group
+    , setuid
+    , setgid
+    , permissions
     , ...
     }:
     ''
-      cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
-      echo -n "${source}" > $wrapperDir/${program}.real
+      cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
+      echo -n "${source}" > "$wrapperDir/${program}.real"
 
       # Prevent races
-      chmod 0000 $wrapperDir/${program}
-      chown ${owner}.${group} $wrapperDir/${program}
+      chmod 0000 "$wrapperDir/${program}"
+      chown ${owner}.${group} "$wrapperDir/${program}"
 
-      chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program}
+      chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}"
     '';
 
   mkWrappedPrograms =
     builtins.map
-      (s: if (s ? capabilities)
-          then mkSetcapProgram
-                 ({ owner = "root";
-                    group = "root";
-                  } // s)
-          else if
-             (s ? setuid && s.setuid) ||
-             (s ? setgid && s.setgid) ||
-             (s ? permissions)
-          then mkSetuidProgram s
-          else mkSetuidProgram
-                 ({ owner  = "root";
-                    group  = "root";
-                    setuid = true;
-                    setgid = false;
-                    permissions = "u+rx,g+x,o+x";
-                  } // s)
-      ) programs;
+      (opts:
+        if opts.capabilities != ""
+        then mkSetcapProgram opts
+        else mkSetuidProgram opts
+      ) (lib.attrValues wrappers);
 in
 {
   imports = [
@@ -95,45 +150,42 @@ in
 
   options = {
     security.wrappers = lib.mkOption {
-      type = lib.types.attrs;
+      type = lib.types.attrsOf wrapperType;
       default = {};
-      example = lib.literalExample
+      example = lib.literalExpression
         ''
-          { sendmail.source = "/nix/store/.../bin/sendmail";
-            ping = {
-              source  = "${pkgs.iputils.out}/bin/ping";
-              owner   = "nobody";
-              group   = "nogroup";
-              capabilities = "cap_net_raw+ep";
-            };
+          {
+            # a setuid root program
+            doas =
+              { setuid = true;
+                owner = "root";
+                group = "root";
+                source = "''${pkgs.doas}/bin/doas";
+              };
+
+            # a setgid program
+            locate =
+              { setgid = true;
+                owner = "root";
+                group = "mlocate";
+                source = "''${pkgs.locate}/bin/locate";
+              };
+
+            # a program with the CAP_NET_RAW capability
+            ping =
+              { owner = "root";
+                group = "root";
+                capabilities = "cap_net_raw+ep";
+                source = "''${pkgs.iputils.out}/bin/ping";
+              };
           }
         '';
       description = ''
-        This option allows the ownership and permissions on the setuid
-        wrappers for specific programs to be overridden from the
-        default (setuid root, but not setgid root).
-
-        <note>
-          <para>The sub-attribute <literal>source</literal> is mandatory,
-          it must be the absolute path to the program to be wrapped.
-          </para>
-
-          <para>The sub-attribute <literal>program</literal> is optional and
-          can give the wrapper program a new name. The default name is the same
-          as the attribute name itself.</para>
-
-          <para>Additionally, this option can set capabilities on a
-          wrapper program that propagates those capabilities down to the
-          wrapped, real program.</para>
-
-          <para>NOTE: cap_setpcap, which is required for the wrapper
-          program to be able to raise caps into the Ambient set is NOT
-          raised to the Ambient set so that the real program cannot
-          modify its own capabilities!! This may be too restrictive for
-          cases in which the real program needs cap_setpcap but it at
-          least leans on the side security paranoid vs. too
-          relaxed.</para>
-        </note>
+        This option effectively allows adding setuid/setgid bits, capabilities,
+        changing file ownership and permissions of a program without directly
+        modifying it. This works by creating a wrapper program under the
+        <option>security.wrapperDir</option> directory, which is then added to
+        the shell <literal>PATH</literal>.
       '';
     };
 
@@ -151,13 +203,31 @@ in
   ###### implementation
   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.util-linux}/bin/mount";
-      umount.source = "${lib.getBin pkgs.util-linux}/bin/umount";
-    };
+    assertions = lib.mapAttrsToList
+      (name: opts:
+        { assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
+          message = ''
+            The security.wrappers.${name} wrapper is not valid:
+                setuid/setgid and capabilities are mutually exclusive.
+          '';
+        }
+      ) wrappers;
+
+    security.wrappers =
+      let
+        mkSetuidRoot = source:
+          { setuid = true;
+            owner = "root";
+            group = "root";
+            inherit source;
+          };
+      in
+      { # These are mount related wrappers that require the +s permission.
+        fusermount  = mkSetuidRoot "${pkgs.fuse}/bin/fusermount";
+        fusermount3 = mkSetuidRoot "${pkgs.fuse3}/bin/fusermount3";
+        mount  = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount";
+        umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount";
+      };
 
     boot.specialFileSystems.${parentWrapperDir} = {
       fsType = "tmpfs";
@@ -179,19 +249,15 @@ in
       ]}"
     '';
 
-    ###### setcap activation script
+    ###### wrappers activation script
     system.activationScripts.wrappers =
       lib.stringAfter [ "specialfs" "users" ]
         ''
-          # Look in the system path and in the default profile for
-          # programs to be wrapped.
-          WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin
-
           chmod 755 "${parentWrapperDir}"
 
           # We want to place the tmpdirs for the wrappers to the parent dir.
           wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
-          chmod a+rx $wrapperDir
+          chmod a+rx "$wrapperDir"
 
           ${lib.concatStringsSep "\n" mkWrappedPrograms}
 
@@ -199,16 +265,44 @@ in
             # Atomically replace the symlink
             # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
             old=$(readlink -f ${wrapperDir})
-            if [ -e ${wrapperDir}-tmp ]; then
-              rm --force --recursive ${wrapperDir}-tmp
+            if [ -e "${wrapperDir}-tmp" ]; then
+              rm --force --recursive "${wrapperDir}-tmp"
             fi
-            ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp
-            mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir}
-            rm --force --recursive $old
+            ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
+            mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
+            rm --force --recursive "$old"
           else
             # For initial setup
-            ln --symbolic $wrapperDir ${wrapperDir}
+            ln --symbolic "$wrapperDir" "${wrapperDir}"
           fi
         '';
+
+    ###### wrappers consistency checks
+    system.extraDependencies = lib.singleton (pkgs.runCommandLocal
+      "ensure-all-wrappers-paths-exist" { }
+      ''
+        # make sure we produce output
+        mkdir -p $out
+
+        echo -n "Checking that Nix store paths of all wrapped programs exist... "
+
+        declare -A wrappers
+        ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v:
+          "wrappers['${n}']='${v.source}'") wrappers)}
+
+        for name in "''${!wrappers[@]}"; do
+          path="''${wrappers[$name]}"
+          if [[ "$path" =~ /nix/store ]] && [ ! -e "$path" ]; then
+            test -t 1 && echo -ne '\033[1;31m'
+            echo "FAIL"
+            echo "The path $path does not exist!"
+            echo 'Please, check the value of `security.wrappers."'$name'".source`.'
+            test -t 1 && echo -ne '\033[0m'
+            exit 1
+          fi
+        done
+
+        echo "OK"
+      '');
   };
 }
diff --git a/nixos/modules/services/admin/meshcentral.nix b/nixos/modules/services/admin/meshcentral.nix
index ae7b6edda7d5a..92762d2037c32 100644
--- a/nixos/modules/services/admin/meshcentral.nix
+++ b/nixos/modules/services/admin/meshcentral.nix
@@ -10,7 +10,7 @@ in with lib; {
       description = "MeshCentral package to use. Replacing this may be necessary to add dependencies for extra functionality.";
       type = types.package;
       default = pkgs.meshcentral;
-      defaultText = "pkgs.meshcentral";
+      defaultText = literalExpression "pkgs.meshcentral";
     };
     settings = mkOption {
       description = ''
diff --git a/nixos/modules/services/admin/oxidized.nix b/nixos/modules/services/admin/oxidized.nix
index 94b44630ba6c4..49ea3ced76a4b 100644
--- a/nixos/modules/services/admin/oxidized.nix
+++ b/nixos/modules/services/admin/oxidized.nix
@@ -33,7 +33,7 @@ in
 
     configFile = mkOption {
       type = types.path;
-      example = literalExample ''
+      example = literalExpression ''
         pkgs.writeText "oxidized-config.yml" '''
           ---
           debug: true
@@ -69,7 +69,7 @@ in
 
     routerDB = mkOption {
       type = types.path;
-      example = literalExample ''
+      example = literalExpression ''
         pkgs.writeText "oxidized-router.db" '''
           hostname-sw1:powerconnect:username1:password2
           hostname-sw2:procurve:username2:password2
diff --git a/nixos/modules/services/amqp/activemq/default.nix b/nixos/modules/services/amqp/activemq/default.nix
index 178b2f6e144bd..47669b05aa91b 100644
--- a/nixos/modules/services/amqp/activemq/default.nix
+++ b/nixos/modules/services/amqp/activemq/default.nix
@@ -33,6 +33,7 @@ in {
       };
       configurationDir = mkOption {
         default = "${activemq}/conf";
+        defaultText = literalExpression ''"''${pkgs.activemq}/conf"'';
         type = types.str;
         description = ''
           The base directory for ActiveMQ's configuration.
@@ -64,7 +65,7 @@ in {
       javaProperties = mkOption {
         type = types.attrs;
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           {
             "java.net.preferIPv4Stack" = "true";
           }
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index 8fdfda9a66d88..3255942fe4386 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -29,7 +29,7 @@ in
       package = mkOption {
         default = pkgs.rabbitmq-server;
         type = types.package;
-        defaultText = "pkgs.rabbitmq-server";
+        defaultText = literalExpression "pkgs.rabbitmq-server";
         description = ''
           Which rabbitmq package to use.
         '';
@@ -82,7 +82,7 @@ in
       configItems = mkOption {
         default = { };
         type = types.attrsOf types.str;
-        example = literalExample ''
+        example = literalExpression ''
           {
             "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
             "auth_backends.1.authz" = "rabbit_auth_backend_internal";
@@ -135,25 +135,14 @@ in
         description = "The list of directories containing external plugins";
       };
 
-      managementPlugin = mkOption {
-        description = "The options to run the management plugin";
-        type = types.submodule {
-          options = {
-            enable = mkOption {
-              default = false;
-              type = types.bool;
-              description = ''
-                Whether to enable the management plugin
-              '';
-            };
-            port = mkOption {
-              default = 15672;
-              type = types.port;
-              description = ''
-                On which port to run the management plugin
-              '';
-            };
-          };
+      managementPlugin = {
+        enable = mkEnableOption "the management plugin";
+        port = mkOption {
+          default = 15672;
+          type = types.port;
+          description = ''
+            On which port to run the management plugin
+          '';
         };
       };
     };
diff --git a/nixos/modules/services/audio/botamusique.nix b/nixos/modules/services/audio/botamusique.nix
index 14614d2dd1613..f4fa0ead4f055 100644
--- a/nixos/modules/services/audio/botamusique.nix
+++ b/nixos/modules/services/audio/botamusique.nix
@@ -17,6 +17,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.botamusique;
+      defaultText = literalExpression "pkgs.botamusique";
       description = "The botamusique package to use.";
     };
 
diff --git a/nixos/modules/services/audio/hqplayerd.nix b/nixos/modules/services/audio/hqplayerd.nix
index d549ac77e0e59..416d12ce21724 100644
--- a/nixos/modules/services/audio/hqplayerd.nix
+++ b/nixos/modules/services/audio/hqplayerd.nix
@@ -63,7 +63,7 @@ in
         description = ''
           HQplayer daemon configuration, written to /etc/hqplayer/hqplayerd.xml.
 
-          Refer to ${pkg}/share/doc/hqplayerd/readme.txt for possible values.
+          Refer to share/doc/hqplayerd/readme.txt in the hqplayerd derivation for possible values.
         '';
       };
     };
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index d0a95b87ee1b6..84fc9957b879f 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -25,8 +25,8 @@ in {
           internal = true;
           type = types.package;
           default = pkgs.jack2;
-          defaultText = "pkgs.jack2";
-          example = literalExample "pkgs.jack1";
+          defaultText = literalExpression "pkgs.jack2";
+          example = literalExpression "pkgs.jack1";
           description = ''
             The JACK package to use.
           '';
@@ -37,7 +37,7 @@ in {
           default = [
             "-dalsa"
           ];
-          example = literalExample ''
+          example = literalExpression ''
             [ "-dalsa" "--device" "hw:1" ];
           '';
           description = ''
diff --git a/nixos/modules/services/audio/liquidsoap.nix b/nixos/modules/services/audio/liquidsoap.nix
index 3a047d10a6314..ffeefc0f988e1 100644
--- a/nixos/modules/services/audio/liquidsoap.nix
+++ b/nixos/modules/services/audio/liquidsoap.nix
@@ -39,9 +39,9 @@ in
       default = {};
 
       example = {
-        myStream1 = literalExample "\"/etc/liquidsoap/myStream1.liq\"";
-        myStream2 = literalExample "./myStream2.liq";
-        myStream3 = literalExample "\"out(playlist(\\\"/srv/music/\\\"))\"";
+        myStream1 = "/etc/liquidsoap/myStream1.liq";
+        myStream2 = literalExpression "./myStream2.liq";
+        myStream3 = "out(playlist(\"/srv/music/\"))";
       };
 
       type = types.attrsOf (types.either types.path types.str);
diff --git a/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix
index 6fd7eae5b892d..9937feadaeb6b 100644
--- a/nixos/modules/services/audio/mopidy.nix
+++ b/nixos/modules/services/audio/mopidy.nix
@@ -39,7 +39,7 @@ in {
       extensionPackages = mkOption {
         default = [];
         type = types.listOf types.package;
-        example = literalExample "[ pkgs.mopidy-spotify ]";
+        example = literalExpression "[ pkgs.mopidy-spotify ]";
         description = ''
           Mopidy extensions that should be loaded by the service.
         '';
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index e33e860d883da..560264e249d0b 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -74,7 +74,7 @@ in {
       musicDirectory = mkOption {
         type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
         default = "${cfg.dataDir}/music";
-        defaultText = "\${dataDir}/music";
+        defaultText = literalExpression ''"''${dataDir}/music"'';
         description = ''
           The directory or NFS/SMB network share where MPD reads music from. If left
           as the default value this directory will automatically be created before
@@ -86,7 +86,7 @@ in {
       playlistDirectory = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/playlists";
-        defaultText = "\${dataDir}/playlists";
+        defaultText = literalExpression ''"''${dataDir}/playlists"'';
         description = ''
           The directory where MPD stores playlists. If left as the default value
           this directory will automatically be created before the MPD server starts,
@@ -155,7 +155,7 @@ in {
       dbFile = mkOption {
         type = types.nullOr types.str;
         default = "${cfg.dataDir}/tag_cache";
-        defaultText = "\${dataDir}/tag_cache";
+        defaultText = literalExpression ''"''${dataDir}/tag_cache"'';
         description = ''
           The path to MPD's database. If set to <literal>null</literal> the
           parameter is omitted from the configuration.
diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
index 42da5a100170f..566c7cae42ce6 100644
--- a/nixos/modules/services/audio/roon-server.nix
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -42,7 +42,7 @@ in {
       environment.ROON_DATAROOT = "/var/lib/${name}";
 
       serviceConfig = {
-        ExecStart = "${pkgs.roon-server}/start.sh";
+        ExecStart = "${pkgs.roon-server}/bin/RoonServer";
         LimitNOFILE = 8192;
         User = cfg.user;
         Group = cfg.group;
diff --git a/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index 21632919699c8..ecd2652849909 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -22,7 +22,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.slimserver;
-        defaultText = "pkgs.slimserver";
+        defaultText = literalExpression "pkgs.slimserver";
         description = "Slimserver package to use.";
       };
 
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index f96b5f3e1942d..d3e97719f3576 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -206,7 +206,7 @@ in {
                 For type <literal>meta</literal>, a list of stream names in the form <literal>/one/two/...</literal>. Don't forget the leading slash.
                 For type <literal>alsa</literal>, use an empty string.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 "/path/to/pipe"
                 "/path/to/librespot"
                 "192.168.1.2:4444"
@@ -226,7 +226,7 @@ in {
               description = ''
                 Key-value pairs that convey additional parameters about a stream.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 # for type == "pipe":
                 {
                   mode = "create";
@@ -254,7 +254,7 @@ in {
         description = ''
           The definition for an input source.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             mpd = {
               type = "pipe";
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
index 9279a03aed4e5..22848ed98000d 100644
--- a/nixos/modules/services/audio/spotifyd.nix
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -4,7 +4,15 @@ with lib;
 
 let
   cfg = config.services.spotifyd;
-  spotifydConf = pkgs.writeText "spotifyd.conf" cfg.config;
+  toml = pkgs.formats.toml {};
+  warnConfig =
+    if cfg.config != ""
+    then lib.trace "Using the stringly typed .config attribute is discouraged. Use the TOML typed .settings attribute instead."
+    else id;
+  spotifydConf =
+    if cfg.settings != {}
+    then toml.generate "spotify.conf" cfg.settings
+    else warnConfig (pkgs.writeText "spotifyd.conf" cfg.config);
 in
 {
   options = {
@@ -15,6 +23,16 @@ in
         default = "";
         type = types.lines;
         description = ''
+          (Deprecated) Configuration for Spotifyd. For syntax and directives, see
+          <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
+        '';
+      };
+
+      settings = mkOption {
+        default = {};
+        type = toml.type;
+        example = { global.bitrate = 320; };
+        description = ''
           Configuration for Spotifyd. For syntax and directives, see
           <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
         '';
@@ -23,6 +41,13 @@ in
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.config == "" || cfg.settings == {};
+        message = "At most one of the .config attribute and the .settings attribute may be set";
+      }
+    ];
+
     systemd.services.spotifyd = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network-online.target" "sound.target" ];
diff --git a/nixos/modules/services/audio/ympd.nix b/nixos/modules/services/audio/ympd.nix
index 551bd941fe687..36c5527027ffa 100644
--- a/nixos/modules/services/audio/ympd.nix
+++ b/nixos/modules/services/audio/ympd.nix
@@ -26,7 +26,6 @@ in {
           type = types.str;
           default = "localhost";
           description = "The host where MPD is listening.";
-          example = "localhost";
         };
 
         port = mkOption {
diff --git a/nixos/modules/services/backup/automysqlbackup.nix b/nixos/modules/services/backup/automysqlbackup.nix
index e3a8d1f79934b..fd2764a40ad2f 100644
--- a/nixos/modules/services/backup/automysqlbackup.nix
+++ b/nixos/modules/services/backup/automysqlbackup.nix
@@ -2,7 +2,7 @@
 
 let
 
-  inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExample;
+  inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExpression;
   inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption optional types;
 
   cfg = config.services.automysqlbackup;
@@ -48,7 +48,7 @@ in
           <filename>''${pkgs.automysqlbackup}/etc/automysqlbackup.conf</filename>
           for details on supported values.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             db_names = [ "nextcloud" "matomo" ];
             table_exclude = [ "nextcloud.oc_users" "nextcloud.oc_whats_new" ];
@@ -73,6 +73,7 @@ in
     services.automysqlbackup.config = mapAttrs (name: mkDefault) {
       mysql_dump_username = user;
       mysql_dump_host = "localhost";
+      mysql_dump_socket = "/run/mysqld/mysqld.sock";
       backup_dir = "/var/backup/mysql";
       db_exclude = [ "information_schema" "performance_schema" ];
       mailcontent = "stdout";
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index ccbc7726392d8..220c571b927e5 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -42,12 +42,16 @@ let
       ${cfg.postInit}
     fi
   '' + ''
-    borg create $extraArgs \
-      --compression ${cfg.compression} \
-      --exclude-from ${mkExcludeFile cfg} \
-      $extraCreateArgs \
-      "::$archiveName$archiveSuffix" \
-      ${escapeShellArgs cfg.paths}
+    (
+      set -o pipefail
+      ${optionalString (cfg.dumpCommand != null) ''${escapeShellArg cfg.dumpCommand} | \''}
+      borg create $extraArgs \
+        --compression ${cfg.compression} \
+        --exclude-from ${mkExcludeFile cfg} \
+        $extraCreateArgs \
+        "::$archiveName$archiveSuffix" \
+        ${if cfg.paths == null then "-" else escapeShellArgs cfg.paths}
+    )
   '' + optionalString cfg.appendFailedSuffix ''
     borg rename $extraArgs \
       "::$archiveName$archiveSuffix" "$archiveName"
@@ -169,6 +173,7 @@ let
         (map (mkAuthorizedKey cfg false) cfg.authorizedKeys
         ++ map (mkAuthorizedKey cfg true) cfg.authorizedKeysAppendOnly);
       useDefaultShell = true;
+      group = cfg.group;
       isSystemUser = true;
     };
     groups.${cfg.group} = { };
@@ -181,6 +186,14 @@ let
       + " without at least one public key";
   };
 
+  mkSourceAssertions = name: cfg: {
+    assertion = count isNull [ cfg.dumpCommand cfg.paths ] == 1;
+    message = ''
+      Exactly one of borgbackup.jobs.${name}.paths or borgbackup.jobs.${name}.dumpCommand
+      must be set.
+    '';
+  };
+
   mkRemovableDeviceAssertions = name: cfg: {
     assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
     message = ''
@@ -202,7 +215,7 @@ in {
       See also the chapter about BorgBackup in the NixOS manual.
     '';
     default = { };
-    example = literalExample ''
+    example = literalExpression ''
       { # for a local backup
         rootBackup = {
           paths = "/";
@@ -239,11 +252,25 @@ in {
         options = {
 
           paths = mkOption {
-            type = with types; coercedTo str lib.singleton (listOf str);
-            description = "Path(s) to back up.";
+            type = with types; nullOr (coercedTo str lib.singleton (listOf str));
+            default = null;
+            description = ''
+              Path(s) to back up.
+              Mutually exclusive with <option>dumpCommand</option>.
+            '';
             example = "/home/user";
           };
 
+          dumpCommand = mkOption {
+            type = with types; nullOr path;
+            default = null;
+            description = ''
+              Backup the stdout of this program instead of filesystem paths.
+              Mutually exclusive with <option>paths</option>.
+            '';
+            example = "/path/to/createZFSsend.sh";
+          };
+
           repo = mkOption {
             type = types.str;
             description = "Remote or local repository to back up to.";
@@ -259,7 +286,7 @@ in {
           archiveBaseName = mkOption {
             type = types.strMatching "[^/{}]+";
             default = "${globalConfig.networking.hostName}-${name}";
-            defaultText = "\${config.networking.hostName}-<name>";
+            defaultText = literalExpression ''"''${config.networking.hostName}-<name>"'';
             description = ''
               How to name the created archives. A timestamp, whose format is
               determined by <option>dateFormat</option>, will be appended. The full
@@ -325,10 +352,7 @@ in {
               you to specify a <option>passCommand</option>
               or a <option>passphrase</option>.
             '';
-            example = ''
-              encryption.mode = "repokey-blake2" ;
-              encryption.passphrase = "mySecretPassphrase" ;
-            '';
+            example = "repokey-blake2";
           };
 
           encryption.passCommand = mkOption {
@@ -436,7 +460,7 @@ in {
               for the available options.
             '';
             default = { };
-            example = literalExample ''
+            example = literalExpression ''
               {
                 within = "1d"; # Keep all archives from the last day
                 daily = 7;
@@ -454,7 +478,7 @@ in {
               Use <literal>""</literal> to consider all archives.
             '';
             default = config.archiveBaseName;
-            defaultText = "\${archiveBaseName}";
+            defaultText = literalExpression "archiveBaseName";
           };
 
           environment = mkOption {
@@ -659,6 +683,7 @@ in {
       assertions =
         mapAttrsToList mkPassAssertion jobs
         ++ mapAttrsToList mkKeysAssertion repos
+        ++ mapAttrsToList mkSourceAssertions jobs
         ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
 
       system.activationScripts = mapAttrs' mkActivationScript jobs;
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index a8ff71f609a5d..0c00b93440506 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -57,7 +57,7 @@ in
         description = "Extra packages for btrbk, like compression utilities for <literal>stream_compress</literal>";
         type = lib.types.listOf lib.types.package;
         default = [ ];
-        example = lib.literalExample "[ pkgs.xz ]";
+        example = lib.literalExpression "[ pkgs.xz ]";
       };
       niceness = lib.mkOption {
         description = "Niceness for local instances of btrbk. Also applies to remote ones connecting via ssh when positive.";
diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix
index bcc135005e16d..562458eb45710 100644
--- a/nixos/modules/services/backup/postgresql-backup.nix
+++ b/nixos/modules/services/backup/postgresql-backup.nix
@@ -85,7 +85,7 @@ in {
 
       backupAll = mkOption {
         default = cfg.databases == [];
-        defaultText = "services.postgresqlBackup.databases == []";
+        defaultText = literalExpression "services.postgresqlBackup.databases == []";
         type = lib.types.bool;
         description = ''
           Backup all databases using pg_dumpall.
diff --git a/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixos/modules/services/backup/postgresql-wal-receiver.nix
index 3d9869d534313..32643adfdaeac 100644
--- a/nixos/modules/services/backup/postgresql-wal-receiver.nix
+++ b/nixos/modules/services/backup/postgresql-wal-receiver.nix
@@ -7,7 +7,7 @@ let
     options = {
       postgresqlPackage = mkOption {
         type = types.package;
-        example = literalExample "pkgs.postgresql_11";
+        example = literalExpression "pkgs.postgresql_11";
         description = ''
           PostgreSQL package to use.
         '';
@@ -15,7 +15,7 @@ let
 
       directory = mkOption {
         type = types.path;
-        example = literalExample "/mnt/pg_wal/main/";
+        example = literalExpression "/mnt/pg_wal/main/";
         description = ''
           Directory to write the output to.
         '';
@@ -88,7 +88,7 @@ let
       extraArgs = mkOption {
         type = with types; listOf str;
         default = [ ];
-        example = literalExample ''
+        example = literalExpression ''
           [
             "--no-sync"
           ]
@@ -101,7 +101,7 @@ let
       environment = mkOption {
         type = with types; attrsOf str;
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           {
             PGPASSFILE = "/private/passfile";
             PGSSLMODE = "require";
@@ -121,7 +121,7 @@ in {
       receivers = mkOption {
         type = with types; attrsOf (submodule receiverSubmodule);
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           {
             main = {
               postgresqlPackage = pkgs.postgresql_11;
diff --git a/nixos/modules/services/backup/restic-rest-server.nix b/nixos/modules/services/backup/restic-rest-server.nix
index d1b775f150dc1..86744637f85d8 100644
--- a/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixos/modules/services/backup/restic-rest-server.nix
@@ -59,7 +59,7 @@ in
 
     package = mkOption {
       default = pkgs.restic-rest-server;
-      defaultText = "pkgs.restic-rest-server";
+      defaultText = literalExpression "pkgs.restic-rest-server";
       type = types.package;
       description = "Restic REST server package to use.";
     };
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index ac57f271526fe..67fef55614b38 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -11,7 +11,7 @@ in
     description = ''
       Periodic backups to create with Restic.
     '';
-    type = types.attrsOf (types.submodule ({ name, ... }: {
+    type = types.attrsOf (types.submodule ({ config, name, ... }: {
       options = {
         passwordFile = mkOption {
           type = types.str;
@@ -21,6 +21,17 @@ in
           example = "/etc/nixos/restic-password";
         };
 
+        environmentFile = mkOption {
+          type = with types; nullOr str;
+          # added on 2021-08-28, s3CredentialsFile should
+          # be removed in the future (+ remember the warning)
+          default = config.s3CredentialsFile;
+          description = ''
+            file containing the credentials to access the repository, in the
+            format of an EnvironmentFile as described by systemd.exec(5)
+          '';
+        };
+
         s3CredentialsFile = mkOption {
           type = with types; nullOr str;
           default = null;
@@ -212,6 +223,7 @@ in
   };
 
   config = {
+    warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups);
     systemd.services =
       mapAttrs' (name: backup:
         let
@@ -251,8 +263,8 @@ in
             RuntimeDirectory = "restic-backups-${name}";
             CacheDirectory = "restic-backups-${name}";
             CacheDirectoryMode = "0700";
-          } // optionalAttrs (backup.s3CredentialsFile != null) {
-            EnvironmentFile = backup.s3CredentialsFile;
+          } // optionalAttrs (backup.environmentFile != null) {
+            EnvironmentFile = backup.environmentFile;
           };
         } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null) {
           preStart = ''
diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix
index 41d0e2e1df686..e70063415ec03 100644
--- a/nixos/modules/services/backup/sanoid.nix
+++ b/nixos/modules/services/backup/sanoid.nix
@@ -57,8 +57,13 @@ let
     useTemplate = use_template;
 
     recursive = mkOption {
-      description = "Whether to recursively snapshot dataset children.";
-      type = types.bool;
+      description = ''
+        Whether to recursively snapshot dataset children.
+        You can also set this to <literal>"zfs"</literal> to handle datasets
+        recursively in an atomic way without the possibility to
+        override settings for child datasets.
+      '';
+      type = with types; oneOf [ bool (enum [ "zfs" ]) ];
       default = false;
     };
 
diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
index 3ad8d279a36d2..4df10f5ee02bf 100644
--- a/nixos/modules/services/backup/syncoid.nix
+++ b/nixos/modules/services/backup/syncoid.nix
@@ -16,16 +16,67 @@ let
     lib.concatMapStrings (s: if lib.isList s then "-" else s)
       (builtins.split "[^a-zA-Z0-9_.\\-]+" name);
 
-  # Function to build "zfs allow" and "zfs unallow" commands for the
-  # filesystems we've delegated permissions to.
-  buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [
-    # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
-    "-+/run/booted-system/sw/bin/zfs"
-    zfsAction
-    cfg.user
-    (concatStringsSep "," permissions)
-    dataset
-  ];
+  # Function to build "zfs allow" commands for the filesystems we've
+  # delegated permissions to. It also checks if the target dataset
+  # exists before delegating permissions, if it doesn't exist we
+  # delegate it to the parent dataset. This should solve the case of
+  # provisoning new datasets.
+  buildAllowCommand = permissions: dataset: (
+    "-+${pkgs.writeShellScript "zfs-allow-${dataset}" ''
+      # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
+
+      # Run a ZFS list on the dataset to check if it exists
+      if ${lib.escapeShellArgs [
+        "/run/booted-system/sw/bin/zfs"
+        "list"
+        dataset
+      ]} 2> /dev/null; then
+        ${lib.escapeShellArgs [
+          "/run/booted-system/sw/bin/zfs"
+          "allow"
+          cfg.user
+          (concatStringsSep "," permissions)
+          dataset
+        ]}
+      else
+        ${lib.escapeShellArgs [
+          "/run/booted-system/sw/bin/zfs"
+          "allow"
+          cfg.user
+          (concatStringsSep "," permissions)
+          # Remove the last part of the path
+          (builtins.dirOf dataset)
+        ]}
+      fi
+    ''}"
+  );
+
+  # Function to build "zfs unallow" commands for the filesystems we've
+  # delegated permissions to. Here we unallow both the target but also
+  # on the parent dataset because at this stage we have no way of
+  # knowing if the allow command did execute on the parent dataset or
+  # not in the pre-hook. We can't run the same if in the post hook
+  # since the dataset should have been created at this point.
+  buildUnallowCommand = permissions: dataset: (
+    "-+${pkgs.writeShellScript "zfs-unallow-${dataset}" ''
+      # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
+      ${lib.escapeShellArgs [
+        "/run/booted-system/sw/bin/zfs"
+        "unallow"
+        cfg.user
+        (concatStringsSep "," permissions)
+        dataset
+      ]}
+      ${lib.escapeShellArgs [
+        "/run/booted-system/sw/bin/zfs"
+        "unallow"
+        cfg.user
+        (concatStringsSep "," permissions)
+        # Remove the last part of the path
+        (builtins.dirOf dataset)
+      ]}
+    ''}"
+  );
 in
 {
 
@@ -235,7 +286,7 @@ in
         };
       }));
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           "pool/test".target = "root@target:pool/test";
         }
@@ -274,11 +325,11 @@ in
             path = [ "/run/booted-system/sw/bin/" ];
             serviceConfig = {
               ExecStartPre =
-                (map (buildAllowCommand "allow" c.localSourceAllow) (localDatasetName c.source)) ++
-                (map (buildAllowCommand "allow" c.localTargetAllow) (localDatasetName c.target));
+                (map (buildAllowCommand c.localSourceAllow) (localDatasetName c.source)) ++
+                (map (buildAllowCommand c.localTargetAllow) (localDatasetName c.target));
               ExecStopPost =
-                (map (buildAllowCommand "unallow" c.localSourceAllow) (localDatasetName c.source)) ++
-                (map (buildAllowCommand "unallow" c.localTargetAllow) (localDatasetName c.target));
+                (map (buildUnallowCommand c.localSourceAllow) (localDatasetName c.source)) ++
+                (map (buildUnallowCommand c.localTargetAllow) (localDatasetName c.target));
               ExecStart = lib.escapeShellArgs ([ "${pkgs.sanoid}/bin/syncoid" ]
                 ++ optionals c.useCommonArgs cfg.commonArgs
                 ++ optional c.recursive "-r"
diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix
index 8187042b4b801..9cce868366123 100644
--- a/nixos/modules/services/backup/tarsnap.nix
+++ b/nixos/modules/services/backup/tarsnap.nix
@@ -214,7 +214,7 @@ in
               maxbwRateUp = mkOption {
                 type = types.nullOr types.int;
                 default = null;
-                example = literalExample "25 * 1000";
+                example = literalExpression "25 * 1000";
                 description = ''
                   Upload bandwidth rate limit in bytes.
                 '';
@@ -223,7 +223,7 @@ in
               maxbwRateDown = mkOption {
                 type = types.nullOr types.int;
                 default = null;
-                example = literalExample "50 * 1000";
+                example = literalExpression "50 * 1000";
                 description = ''
                   Download bandwidth rate limit in bytes.
                 '';
@@ -256,7 +256,7 @@ in
 
         default = {};
 
-        example = literalExample ''
+        example = literalExpression ''
           {
             nixos =
               { directories = [ "/home" "/root/ssl" ];
@@ -310,7 +310,7 @@ in
         # the service - therefore we sleep in a loop until we can ping the
         # endpoint.
         preStart = ''
-          while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
+          while ! ping -4 -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
         '';
 
         script = let
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index debb2a397050a..09e60177c3909 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -166,8 +166,8 @@ let
           <option>postsnap</option>.
         '';
         default = null;
-        example = literalExample ''
-          ''${pkgs.mariadb}/bin/mysql -e "set autocommit=0;flush tables with read lock;\\! ''${pkgs.coreutils}/bin/sleep 600" &  ''${pkgs.coreutils}/bin/echo $! > /tmp/mariadblock.pid ; sleep 10
+        example = literalExpression ''
+          '''''${pkgs.mariadb}/bin/mysql -e "set autocommit=0;flush tables with read lock;\\! ''${pkgs.coreutils}/bin/sleep 600" &  ''${pkgs.coreutils}/bin/echo $! > /tmp/mariadblock.pid ; sleep 10'''
         '';
       };
 
@@ -178,8 +178,8 @@ let
           e.g. for database unlocking. See also <option>presnap</option>.
         '';
         default = null;
-        example = literalExample ''
-          ''${pkgs.coreutils}/bin/kill `''${pkgs.coreutils}/bin/cat /tmp/mariadblock.pid`;''${pkgs.coreutils}/bin/rm /tmp/mariadblock.pid
+        example = literalExpression ''
+          "''${pkgs.coreutils}/bin/kill `''${pkgs.coreutils}/bin/cat /tmp/mariadblock.pid`;''${pkgs.coreutils}/bin/rm /tmp/mariadblock.pid"
         '';
       };
 
@@ -223,7 +223,7 @@ let
         type = attrsOf (destType config);
         description = "Additional destinations.";
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             local = {
               dataset = "btank/backup";
@@ -324,14 +324,14 @@ in
       autoCreation = mkOption {
         type = bool;
         default = false;
-        description = "Automatically create the destination dataset if it does not exists.";
+        description = "Automatically create the destination dataset if it does not exist.";
       };
 
       zetup = mkOption {
         type = attrsOf srcType;
         description = "Znapzend configuration.";
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "tank/home" = {
               # Make snapshots of tank/home every hour, keep those for 1 day,
diff --git a/nixos/modules/services/blockchain/ethereum/geth.nix b/nixos/modules/services/blockchain/ethereum/geth.nix
index 6c2df95886e7f..bf2cf1edd4d8d 100644
--- a/nixos/modules/services/blockchain/ethereum/geth.nix
+++ b/nixos/modules/services/blockchain/ethereum/geth.nix
@@ -108,6 +108,7 @@ let
 
       package = mkOption {
         default = pkgs.go-ethereum.geth;
+        defaultText = literalExpression "pkgs.go-ethereum.geth";
         type = types.package;
         description = "Package to use as Go Ethereum node.";
       };
diff --git a/nixos/modules/services/cluster/hadoop/conf.nix b/nixos/modules/services/cluster/hadoop/conf.nix
index 38db10406b9a0..0caec5cfc203f 100644
--- a/nixos/modules/services/cluster/hadoop/conf.nix
+++ b/nixos/modules/services/cluster/hadoop/conf.nix
@@ -1,4 +1,4 @@
-{ hadoop, pkgs }:
+{ cfg, pkgs, lib }:
 let
   propertyXml = name: value: ''
     <property>
@@ -13,19 +13,32 @@ let
       ${builtins.concatStringsSep "\n" (pkgs.lib.mapAttrsToList propertyXml properties)}
     </configuration>
   '';
+  cfgLine = name: value: ''
+    ${name}=${builtins.toString value}
+  '';
+  cfgFile = fileName: properties: pkgs.writeTextDir fileName ''
+    # generated by NixOS
+    ${builtins.concatStringsSep "" (pkgs.lib.mapAttrsToList cfgLine properties)}
+  '';
   userFunctions = ''
     hadoop_verify_logdir() {
       echo Skipping verification of log directory
     }
   '';
+  hadoopEnv = ''
+    export HADOOP_LOG_DIR=/tmp/hadoop/$USER
+  '';
 in
-pkgs.buildEnv {
-  name = "hadoop-conf";
-  paths = [
-    (siteXml "core-site.xml" hadoop.coreSite)
-    (siteXml "hdfs-site.xml" hadoop.hdfsSite)
-    (siteXml "mapred-site.xml" hadoop.mapredSite)
-    (siteXml "yarn-site.xml" hadoop.yarnSite)
-    (pkgs.writeTextDir "hadoop-user-functions.sh" userFunctions)
-  ];
-}
+pkgs.runCommand "hadoop-conf" {} ''
+  mkdir -p $out/
+  cp ${siteXml "core-site.xml" cfg.coreSite}/* $out/
+  cp ${siteXml "hdfs-site.xml" cfg.hdfsSite}/* $out/
+  cp ${siteXml "mapred-site.xml" cfg.mapredSite}/* $out/
+  cp ${siteXml "yarn-site.xml" cfg.yarnSite}/* $out/
+  cp ${siteXml "httpfs-site.xml" cfg.httpfsSite}/* $out/
+  cp ${cfgFile "container-executor.cfg" cfg.containerExecutorCfg}/* $out/
+  cp ${pkgs.writeTextDir "hadoop-user-functions.sh" userFunctions}/* $out/
+  cp ${pkgs.writeTextDir "hadoop-env.sh" hadoopEnv}/* $out/
+  cp ${cfg.log4jProperties} $out/log4j.properties
+  ${lib.concatMapStringsSep "\n" (dir: "cp -r ${dir}/* $out/") cfg.extraConfDirs}
+''
diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix
index 41ac46e538e35..90f22c48e0552 100644
--- a/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixos/modules/services/cluster/hadoop/default.nix
@@ -1,5 +1,7 @@
 { config, lib, pkgs, ...}:
-
+let
+  cfg = config.services.hadoop;
+in
 with lib;
 {
   imports = [ ./yarn.nix ./hdfs.nix ];
@@ -8,52 +10,136 @@ with lib;
     coreSite = mkOption {
       default = {};
       type = types.attrsOf types.anything;
-      example = literalExample ''
+      example = literalExpression ''
         {
           "fs.defaultFS" = "hdfs://localhost";
         }
       '';
-      description = "Hadoop core-site.xml definition";
+      description = ''
+        Hadoop core-site.xml definition
+        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/core-default.xml"/>
+      '';
     };
 
     hdfsSite = mkOption {
-      default = {};
+      default = {
+        "dfs.namenode.rpc-bind-host" = "0.0.0.0";
+      };
       type = types.attrsOf types.anything;
-      example = literalExample ''
+      example = literalExpression ''
         {
           "dfs.nameservices" = "namenode1";
         }
       '';
-      description = "Hadoop hdfs-site.xml definition";
+      description = ''
+        Hadoop hdfs-site.xml definition
+        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml"/>
+      '';
     };
 
     mapredSite = mkOption {
-      default = {};
+      default = {
+        "mapreduce.framework.name" = "yarn";
+        "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
+        "mapreduce.map.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
+        "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
+      };
       type = types.attrsOf types.anything;
-      example = literalExample ''
-        {
-          "mapreduce.map.cpu.vcores" = "1";
+      example = literalExpression ''
+        options.services.hadoop.mapredSite.default // {
+          "mapreduce.map.java.opts" = "-Xmx900m -XX:+UseParallelGC";
         }
       '';
-      description = "Hadoop mapred-site.xml definition";
+      description = ''
+        Hadoop mapred-site.xml definition
+        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/mapred-default.xml"/>
+      '';
     };
 
     yarnSite = mkOption {
-      default = {};
+      default = {
+        "yarn.nodemanager.admin-env" = "PATH=$PATH";
+        "yarn.nodemanager.aux-services" = "mapreduce_shuffle";
+        "yarn.nodemanager.aux-services.mapreduce_shuffle.class" = "org.apache.hadoop.mapred.ShuffleHandler";
+        "yarn.nodemanager.bind-host" = "0.0.0.0";
+        "yarn.nodemanager.container-executor.class" = "org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor";
+        "yarn.nodemanager.env-whitelist" = "JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_HOME,LANG,TZ";
+        "yarn.nodemanager.linux-container-executor.group" = "hadoop";
+        "yarn.nodemanager.linux-container-executor.path" = "/run/wrappers/yarn-nodemanager/bin/container-executor";
+        "yarn.nodemanager.log-dirs" = "/var/log/hadoop/yarn/nodemanager";
+        "yarn.resourcemanager.bind-host" = "0.0.0.0";
+        "yarn.resourcemanager.scheduler.class" = "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler";
+      };
+      type = types.attrsOf types.anything;
+      example = literalExpression ''
+        options.services.hadoop.yarnSite.default // {
+          "yarn.resourcemanager.hostname" = "''${config.networking.hostName}";
+        }
+      '';
+      description = ''
+        Hadoop yarn-site.xml definition
+        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-common/yarn-default.xml"/>
+      '';
+    };
+
+    httpfsSite = mkOption {
+      default = { };
       type = types.attrsOf types.anything;
-      example = literalExample ''
+      example = literalExpression ''
         {
-          "yarn.resourcemanager.ha.id" = "resourcemanager1";
+          "hadoop.http.max.threads" = 500;
+        }
+      '';
+      description = ''
+        Hadoop httpfs-site.xml definition
+        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-hdfs-httpfs/httpfs-default.html"/>
+      '';
+    };
+
+    log4jProperties = mkOption {
+      default = "${cfg.package}/lib/${cfg.package.untarDir}/etc/hadoop/log4j.properties";
+      type = types.path;
+      example = literalExpression ''
+        "''${pkgs.hadoop}/lib/''${pkgs.hadoop.untarDir}/etc/hadoop/log4j.properties";
+      '';
+      description = "log4j.properties file added to HADOOP_CONF_DIR";
+    };
+
+    containerExecutorCfg = mkOption {
+      default = {
+        # must be the same as yarn.nodemanager.linux-container-executor.group in yarnSite
+        "yarn.nodemanager.linux-container-executor.group"="hadoop";
+        "min.user.id"=1000;
+        "feature.terminal.enabled"=1;
+      };
+      type = types.attrsOf types.anything;
+      example = literalExpression ''
+        options.services.hadoop.containerExecutorCfg.default // {
+          "feature.terminal.enabled" = 0;
         }
       '';
-      description = "Hadoop yarn-site.xml definition";
+      description = ''
+        Yarn container-executor.cfg definition
+        <link xlink:href="https://hadoop.apache.org/docs/r2.7.2/hadoop-yarn/hadoop-yarn-site/SecureContainer.html"/>
+      '';
+    };
+
+    extraConfDirs = mkOption {
+      default = [];
+      type = types.listOf types.path;
+      example = literalExpression ''
+        [
+          ./extraHDFSConfs
+          ./extraYARNConfs
+        ]
+      '';
+      description = "Directories containing additional config files to be added to HADOOP_CONF_DIR";
     };
 
     package = mkOption {
       type = types.package;
       default = pkgs.hadoop;
-      defaultText = "pkgs.hadoop";
-      example = literalExample "pkgs.hadoop";
+      defaultText = literalExpression "pkgs.hadoop";
       description = "";
     };
   };
@@ -61,10 +147,17 @@ with lib;
 
   config = mkMerge [
     (mkIf (builtins.hasAttr "yarn" config.users.users ||
-           builtins.hasAttr "hdfs" config.users.users) {
+           builtins.hasAttr "hdfs" config.users.users ||
+           builtins.hasAttr "httpfs" config.users.users) {
       users.groups.hadoop = {
         gid = config.ids.gids.hadoop;
       };
+      environment = {
+        systemPackages = [ cfg.package ];
+        etc."hadoop-conf".source = let
+          hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
+        in "${hadoopConf}";
+      };
     })
 
   ];
diff --git a/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixos/modules/services/cluster/hadoop/hdfs.nix
index 4f4b0a92108fa..be667aa82d8a6 100644
--- a/nixos/modules/services/cluster/hadoop/hdfs.nix
+++ b/nixos/modules/services/cluster/hadoop/hdfs.nix
@@ -1,66 +1,190 @@
 { config, lib, pkgs, ...}:
+with lib;
 let
   cfg = config.services.hadoop;
-  hadoopConf = import ./conf.nix { hadoop = cfg; pkgs = pkgs; };
+  hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
+  restartIfChanged  = mkOption {
+    type = types.bool;
+    description = ''
+      Automatically restart the service on config change.
+      This can be set to false to defer restarts on clusters running critical applications.
+      Please consider the security implications of inadvertently running an older version,
+      and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+    '';
+    default = false;
+  };
 in
-with lib;
 {
   options.services.hadoop.hdfs = {
-    namenode.enabled = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to run the Hadoop YARN NameNode
-      '';
+    namenode = {
+      enable = mkEnableOption "Whether to run the HDFS NameNode";
+      formatOnInit = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Format HDFS namenode on first start. This is useful for quickly spinning up ephemeral HDFS clusters with a single namenode.
+          For HA clusters, initialization involves multiple steps across multiple nodes. Follow [this guide](https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html)
+          to initialize an HA cluster manually.
+        '';
+      };
+      inherit restartIfChanged;
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Open firewall ports for namenode
+        '';
+      };
+    };
+    datanode = {
+      enable = mkEnableOption "Whether to run the HDFS DataNode";
+      inherit restartIfChanged;
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Open firewall ports for datanode
+        '';
+      };
+    };
+    journalnode = {
+      enable = mkEnableOption "Whether to run the HDFS JournalNode";
+      inherit restartIfChanged;
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Open firewall ports for journalnode
+        '';
+      };
+    };
+    zkfc = {
+      enable = mkEnableOption "Whether to run the HDFS ZooKeeper failover controller";
+      inherit restartIfChanged;
     };
-    datanode.enabled = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to run the Hadoop YARN DataNode
-      '';
+    httpfs = {
+      enable = mkEnableOption "Whether to run the HDFS HTTPfs server";
+      tempPath = mkOption {
+        type = types.path;
+        default = "/tmp/hadoop/httpfs";
+        description = ''
+          HTTPFS_TEMP path used by HTTPFS
+        '';
+      };
+      inherit restartIfChanged;
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Open firewall ports for HTTPFS
+        '';
+      };
     };
   };
 
   config = mkMerge [
-    (mkIf cfg.hdfs.namenode.enabled {
+    (mkIf cfg.hdfs.namenode.enable {
       systemd.services.hdfs-namenode = {
         description = "Hadoop HDFS NameNode";
         wantedBy = [ "multi-user.target" ];
+        inherit (cfg.hdfs.namenode) restartIfChanged;
 
-        environment = {
-          HADOOP_HOME = "${cfg.package}";
-        };
-
-        preStart = ''
+        preStart = (mkIf cfg.hdfs.namenode.formatOnInit ''
           ${cfg.package}/bin/hdfs --config ${hadoopConf} namenode -format -nonInteractive || true
-        '';
+        '');
 
         serviceConfig = {
           User = "hdfs";
           SyslogIdentifier = "hdfs-namenode";
           ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} namenode";
+          Restart = "always";
         };
       };
+
+      networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.namenode.openFirewall [
+        9870 # namenode.http-address
+        8020 # namenode.rpc-address
+        8022 # namenode. servicerpc-address
+      ]);
     })
-    (mkIf cfg.hdfs.datanode.enabled {
+    (mkIf cfg.hdfs.datanode.enable {
       systemd.services.hdfs-datanode = {
         description = "Hadoop HDFS DataNode";
         wantedBy = [ "multi-user.target" ];
-
-        environment = {
-          HADOOP_HOME = "${cfg.package}";
-        };
+        inherit (cfg.hdfs.datanode) restartIfChanged;
 
         serviceConfig = {
           User = "hdfs";
           SyslogIdentifier = "hdfs-datanode";
           ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} datanode";
+          Restart = "always";
         };
       };
+
+      networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.datanode.openFirewall [
+        9864 # datanode.http.address
+        9866 # datanode.address
+        9867 # datanode.ipc.address
+      ]);
+    })
+    (mkIf cfg.hdfs.journalnode.enable {
+      systemd.services.hdfs-journalnode = {
+        description = "Hadoop HDFS JournalNode";
+        wantedBy = [ "multi-user.target" ];
+        inherit (cfg.hdfs.journalnode) restartIfChanged;
+
+        serviceConfig = {
+          User = "hdfs";
+          SyslogIdentifier = "hdfs-journalnode";
+          ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} journalnode";
+          Restart = "always";
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.journalnode.openFirewall [
+        8480 # dfs.journalnode.http-address
+        8485 # dfs.journalnode.rpc-address
+      ]);
+    })
+    (mkIf cfg.hdfs.zkfc.enable {
+      systemd.services.hdfs-zkfc = {
+        description = "Hadoop HDFS ZooKeeper failover controller";
+        wantedBy = [ "multi-user.target" ];
+        inherit (cfg.hdfs.zkfc) restartIfChanged;
+
+        serviceConfig = {
+          User = "hdfs";
+          SyslogIdentifier = "hdfs-zkfc";
+          ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} zkfc";
+          Restart = "always";
+        };
+      };
+    })
+    (mkIf cfg.hdfs.httpfs.enable {
+      systemd.services.hdfs-httpfs = {
+        description = "Hadoop httpfs";
+        wantedBy = [ "multi-user.target" ];
+        inherit (cfg.hdfs.httpfs) restartIfChanged;
+
+        environment.HTTPFS_TEMP = cfg.hdfs.httpfs.tempPath;
+
+        preStart = ''
+          mkdir -p $HTTPFS_TEMP
+        '';
+
+        serviceConfig = {
+          User = "httpfs";
+          SyslogIdentifier = "hdfs-httpfs";
+          ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} httpfs";
+          Restart = "always";
+        };
+      };
+      networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.httpfs.openFirewall [
+        14000 # httpfs.http.port
+      ]);
     })
     (mkIf (
-        cfg.hdfs.namenode.enabled || cfg.hdfs.datanode.enabled
+        cfg.hdfs.namenode.enable || cfg.hdfs.datanode.enable || cfg.hdfs.journalnode.enable || cfg.hdfs.zkfc.enable
     ) {
       users.users.hdfs = {
         description = "Hadoop HDFS user";
@@ -68,6 +192,12 @@ with lib;
         uid = config.ids.uids.hdfs;
       };
     })
-
+    (mkIf cfg.hdfs.httpfs.enable {
+      users.users.httpfs = {
+        description = "Hadoop HTTPFS user";
+        group = "hadoop";
+        isSystemUser = true;
+      };
+    })
   ];
 }
diff --git a/nixos/modules/services/cluster/hadoop/yarn.nix b/nixos/modules/services/cluster/hadoop/yarn.nix
index c92020637e476..37c26ea10f76f 100644
--- a/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -1,30 +1,56 @@
 { config, lib, pkgs, ...}:
+with lib;
 let
   cfg = config.services.hadoop;
-  hadoopConf = import ./conf.nix { hadoop = cfg; pkgs = pkgs; };
+  hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
+  restartIfChanged  = mkOption {
+    type = types.bool;
+    description = ''
+      Automatically restart the service on config change.
+      This can be set to false to defer restarts on clusters running critical applications.
+      Please consider the security implications of inadvertently running an older version,
+      and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+    '';
+    default = false;
+  };
 in
-with lib;
 {
   options.services.hadoop.yarn = {
-    resourcemanager.enabled = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to run the Hadoop YARN ResourceManager
-      '';
+    resourcemanager = {
+      enable = mkEnableOption "Whether to run the Hadoop YARN ResourceManager";
+      inherit restartIfChanged;
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Open firewall ports for resourcemanager
+        '';
+      };
     };
-    nodemanager.enabled = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to run the Hadoop YARN NodeManager
-      '';
+    nodemanager = {
+      enable = mkEnableOption "Whether to run the Hadoop YARN NodeManager";
+      inherit restartIfChanged;
+      addBinBash = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Add /bin/bash. This is needed by the linux container executor's launch script.
+        '';
+      };
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Open firewall ports for nodemanager.
+          Because containers can listen on any ephemeral port, TCP ports 1024–65535 will be opened.
+        '';
+      };
     };
   };
 
   config = mkMerge [
     (mkIf (
-        cfg.yarn.resourcemanager.enabled || cfg.yarn.nodemanager.enabled
+        cfg.yarn.resourcemanager.enable || cfg.yarn.nodemanager.enable
     ) {
 
       users.users.yarn = {
@@ -34,40 +60,68 @@ with lib;
       };
     })
 
-    (mkIf cfg.yarn.resourcemanager.enabled {
+    (mkIf cfg.yarn.resourcemanager.enable {
       systemd.services.yarn-resourcemanager = {
         description = "Hadoop YARN ResourceManager";
         wantedBy = [ "multi-user.target" ];
-
-        environment = {
-          HADOOP_HOME = "${cfg.package}";
-        };
+        inherit (cfg.yarn.resourcemanager) restartIfChanged;
 
         serviceConfig = {
           User = "yarn";
           SyslogIdentifier = "yarn-resourcemanager";
           ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " +
                       " resourcemanager";
+          Restart = "always";
         };
       };
+      networking.firewall.allowedTCPPorts = (mkIf cfg.yarn.resourcemanager.openFirewall [
+        8088 # resourcemanager.webapp.address
+        8030 # resourcemanager.scheduler.address
+        8031 # resourcemanager.resource-tracker.address
+        8032 # resourcemanager.address
+        8033 # resourcemanager.admin.address
+      ]);
     })
 
-    (mkIf cfg.yarn.nodemanager.enabled {
+    (mkIf cfg.yarn.nodemanager.enable {
+      # Needed because yarn hardcodes /bin/bash in container start scripts
+      # These scripts can't be patched, they are generated at runtime
+      systemd.tmpfiles.rules = [
+        (mkIf cfg.yarn.nodemanager.addBinBash "L /bin/bash - - - - /run/current-system/sw/bin/bash")
+      ];
+
       systemd.services.yarn-nodemanager = {
         description = "Hadoop YARN NodeManager";
         wantedBy = [ "multi-user.target" ];
+        inherit (cfg.yarn.nodemanager) restartIfChanged;
 
-        environment = {
-          HADOOP_HOME = "${cfg.package}";
-        };
+        preStart = ''
+          # create log dir
+          mkdir -p /var/log/hadoop/yarn/nodemanager
+          chown yarn:hadoop /var/log/hadoop/yarn/nodemanager
+
+          # set up setuid container executor binary
+          rm -rf /run/wrappers/yarn-nodemanager/ || true
+          mkdir -p /run/wrappers/yarn-nodemanager/{bin,etc/hadoop}
+          cp ${cfg.package}/lib/${cfg.package.untarDir}/bin/container-executor /run/wrappers/yarn-nodemanager/bin/
+          chgrp hadoop /run/wrappers/yarn-nodemanager/bin/container-executor
+          chmod 6050 /run/wrappers/yarn-nodemanager/bin/container-executor
+          cp ${hadoopConf}/container-executor.cfg /run/wrappers/yarn-nodemanager/etc/hadoop/
+        '';
 
         serviceConfig = {
           User = "yarn";
           SyslogIdentifier = "yarn-nodemanager";
+          PermissionsStartOnly = true;
           ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " +
                       " nodemanager";
+          Restart = "always";
         };
       };
+
+      networking.firewall.allowedTCPPortRanges = [
+        (mkIf (cfg.yarn.nodemanager.openFirewall) {from = 1024; to = 65535;})
+      ];
     })
 
   ];
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index e5c51441690ac..50b6780bbe662 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -12,8 +12,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.k3s;
-      defaultText = "pkgs.k3s";
-      example = literalExample "pkgs.k3s";
+      defaultText = literalExpression "pkgs.k3s";
       description = "Package that should be used for k3s";
     };
 
diff --git a/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index 821f1aa54604f..3d988dc2479ac 100644
--- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -27,7 +27,7 @@ in
       '';
       default = { };
       type = attrsOf attrs;
-      example = literalExample ''
+      example = literalExpression ''
         {
           "my-service" = {
             "apiVersion" = "v1";
@@ -46,7 +46,7 @@ in
       description = "Kubernetes addons (any kind of Kubernetes resource can be an addon).";
       default = { };
       type = attrsOf (either attrs (listOf attrs));
-      example = literalExample ''
+      example = literalExpression ''
         {
           "my-service" = {
             "apiVersion" = "v1";
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 8f937a13231b2..34943fddd3d10 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -83,21 +83,24 @@ in {
           reload
           loadbalance
         }'';
-      defaultText = ''
-        .:${toString ports.dns} {
-          errors
-          health :${toString ports.health}
-          kubernetes ''${config.services.kubernetes.addons.dns.clusterDomain} in-addr.arpa ip6.arpa {
-            pods insecure
-            fallthrough in-addr.arpa ip6.arpa
+      defaultText = literalExpression ''
+        '''
+          .:${toString ports.dns} {
+            errors
+            health :${toString ports.health}
+            kubernetes ''${config.services.kubernetes.addons.dns.clusterDomain} in-addr.arpa ip6.arpa {
+              pods insecure
+              fallthrough in-addr.arpa ip6.arpa
+            }
+            prometheus :${toString ports.metrics}
+            forward . /etc/resolv.conf
+            cache 30
+            loop
+            reload
+            loadbalance
           }
-          prometheus :${toString ports.metrics}
-          forward . /etc/resolv.conf
-          cache 30
-          loop
-          reload
-          loadbalance
-        }'';
+        '''
+      '';
     };
   };
 
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 33d217ba60eda..433adf4d488c8 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -5,28 +5,33 @@ with lib;
 let
   cfg = config.services.kubernetes;
 
-  defaultContainerdConfigFile = pkgs.writeText "containerd.toml" ''
-    version = 2
-    root = "/var/lib/containerd"
-    state = "/run/containerd"
-    oom_score = 0
-
-    [grpc]
-      address = "/run/containerd/containerd.sock"
+  defaultContainerdSettings = {
+    version = 2;
+    root = "/var/lib/containerd";
+    state = "/run/containerd";
+    oom_score = 0;
+
+    grpc = {
+      address = "/run/containerd/containerd.sock";
+    };
 
-    [plugins."io.containerd.grpc.v1.cri"]
-      sandbox_image = "pause:latest"
+    plugins."io.containerd.grpc.v1.cri" = {
+      sandbox_image = "pause:latest";
 
-    [plugins."io.containerd.grpc.v1.cri".cni]
-      bin_dir = "/opt/cni/bin"
-      max_conf_num = 0
+      cni = {
+        bin_dir = "/opt/cni/bin";
+        max_conf_num = 0;
+      };
 
-    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
-      runtime_type = "io.containerd.runc.v2"
+      containerd.runtimes.runc = {
+        runtime_type = "io.containerd.runc.v2";
+      };
 
-    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."io.containerd.runc.v2".options]
-      SystemdCgroup = true
-  '';
+      containerd.runtimes."io.containerd.runc.v2".options = {
+        SystemdCgroup = true;
+      };
+    };
+  };
 
   mkKubeConfig = name: conf: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON {
     apiVersion = "v1";
@@ -121,7 +126,7 @@ in {
       description = "Kubernetes package to use.";
       type = types.package;
       default = pkgs.kubernetes;
-      defaultText = "pkgs.kubernetes";
+      defaultText = literalExpression "pkgs.kubernetes";
     };
 
     kubeconfig = mkKubeConfigOptions "Default kubeconfig";
@@ -248,7 +253,7 @@ in {
     (mkIf cfg.kubelet.enable {
       virtualisation.containerd = {
         enable = mkDefault true;
-        configFile = mkDefault defaultContainerdConfigFile;
+        settings = mapAttrsRecursive (name: mkDefault) defaultContainerdSettings;
       };
     })
 
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 08f5cdfdf3347..eb0cb1f3dbc0c 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -96,7 +96,7 @@ in
         description = "Kubernetes CNI configuration.";
         type = listOf attrs;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [{
             "cniVersion": "0.3.1",
             "name": "mynet",
@@ -258,6 +258,8 @@ in
         "net.bridge.bridge-nf-call-ip6tables" = 1;
       };
 
+      systemd.enableUnifiedCgroupHierarchy = false; # true breaks node memory metrics
+
       systemd.services.kubelet = {
         description = "Kubernetes Kubelet Service";
         wantedBy = [ "kubernetes.target" ];
@@ -343,7 +345,7 @@ in
       };
 
       # Allways include cni plugins
-      services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins];
+      services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins pkgs.cni-plugin-flannel];
 
       boot.kernelModules = ["br_netfilter" "overlay"];
 
diff --git a/nixos/modules/services/cluster/spark/default.nix b/nixos/modules/services/cluster/spark/default.nix
new file mode 100644
index 0000000000000..e6b44e130a3e0
--- /dev/null
+++ b/nixos/modules/services/cluster/spark/default.nix
@@ -0,0 +1,162 @@
+{config, pkgs, lib, ...}:
+let
+  cfg = config.services.spark;
+in
+with lib;
+{
+  options = {
+    services.spark = {
+      master = {
+        enable = mkEnableOption "Spark master service";
+        bind = mkOption {
+          type = types.str;
+          description = "Address the spark master binds to.";
+          default = "127.0.0.1";
+          example = "0.0.0.0";
+        };
+        restartIfChanged  = mkOption {
+          type = types.bool;
+          description = ''
+            Automatically restart master service on config change.
+            This can be set to false to defer restarts on clusters running critical applications.
+            Please consider the security implications of inadvertently running an older version,
+            and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+          '';
+          default = true;
+        };
+        extraEnvironment = mkOption {
+          type = types.attrsOf types.str;
+          description = "Extra environment variables to pass to spark master. See spark-standalone documentation.";
+          default = {};
+          example = {
+            SPARK_MASTER_WEBUI_PORT = 8181;
+            SPARK_MASTER_OPTS = "-Dspark.deploy.defaultCores=5";
+          };
+        };
+      };
+      worker = {
+        enable = mkEnableOption "Spark worker service";
+        workDir = mkOption {
+          type = types.path;
+          description = "Spark worker work dir.";
+          default = "/var/lib/spark";
+        };
+        master = mkOption {
+          type = types.str;
+          description = "Address of the spark master.";
+          default = "127.0.0.1:7077";
+        };
+        restartIfChanged  = mkOption {
+          type = types.bool;
+          description = ''
+            Automatically restart worker service on config change.
+            This can be set to false to defer restarts on clusters running critical applications.
+            Please consider the security implications of inadvertently running an older version,
+            and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+          '';
+          default = true;
+        };
+        extraEnvironment = mkOption {
+          type = types.attrsOf types.str;
+          description = "Extra environment variables to pass to spark worker.";
+          default = {};
+          example = {
+            SPARK_WORKER_CORES = 5;
+            SPARK_WORKER_MEMORY = "2g";
+          };
+        };
+      };
+      confDir = mkOption {
+        type = types.path;
+        description = "Spark configuration directory. Spark will use the configuration files (spark-defaults.conf, spark-env.sh, log4j.properties, etc) from this directory.";
+        default = "${cfg.package}/lib/${cfg.package.untarDir}/conf";
+        defaultText = literalExpression ''"''${package}/lib/''${package.untarDir}/conf"'';
+      };
+      logDir = mkOption {
+        type = types.path;
+        description = "Spark log directory.";
+        default = "/var/log/spark";
+      };
+      package = mkOption {
+        type = types.package;
+        description = "Spark package.";
+        default = pkgs.spark;
+        defaultText = literalExpression "pkgs.spark";
+        example = literalExpression ''pkgs.spark.overrideAttrs (super: rec {
+          pname = "spark";
+          version = "2.4.4";
+
+          src = pkgs.fetchzip {
+            url    = "mirror://apache/spark/"''${pname}-''${version}/''${pname}-''${version}-bin-without-hadoop.tgz";
+            sha256 = "1a9w5k0207fysgpxx6db3a00fs5hdc2ncx99x4ccy2s0v5ndc66g";
+          };
+        })'';
+      };
+    };
+  };
+  config = lib.mkIf (cfg.worker.enable || cfg.master.enable) {
+    environment.systemPackages = [ cfg.package ];
+    systemd = {
+      services = {
+        spark-master = lib.mkIf cfg.master.enable {
+          path = with pkgs; [ procps openssh nettools ];
+          description = "spark master service.";
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          restartIfChanged = cfg.master.restartIfChanged;
+          environment = cfg.master.extraEnvironment // {
+            SPARK_MASTER_HOST = cfg.master.bind;
+            SPARK_CONF_DIR = cfg.confDir;
+            SPARK_LOG_DIR = cfg.logDir;
+          };
+          serviceConfig = {
+            Type = "forking";
+            User = "spark";
+            Group = "spark";
+            WorkingDirectory = "${cfg.package}/lib/${cfg.package.untarDir}";
+            ExecStart = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/start-master.sh";
+            ExecStop  = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/stop-master.sh";
+            TimeoutSec = 300;
+            StartLimitBurst=10;
+            Restart = "always";
+          };
+        };
+        spark-worker = lib.mkIf cfg.worker.enable {
+          path = with pkgs; [ procps openssh nettools rsync ];
+          description = "spark master service.";
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          restartIfChanged = cfg.worker.restartIfChanged;
+          environment = cfg.worker.extraEnvironment // {
+            SPARK_MASTER = cfg.worker.master;
+            SPARK_CONF_DIR = cfg.confDir;
+            SPARK_LOG_DIR = cfg.logDir;
+            SPARK_WORKER_DIR = cfg.worker.workDir;
+          };
+          serviceConfig = {
+            Type = "forking";
+            User = "spark";
+            WorkingDirectory = "${cfg.package}/lib/${cfg.package.untarDir}";
+            ExecStart = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/start-worker.sh spark://${cfg.worker.master}";
+            ExecStop  = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/stop-worker.sh";
+            TimeoutSec = 300;
+            StartLimitBurst=10;
+            Restart = "always";
+          };
+        };
+      };
+      tmpfiles.rules = [
+        "d '${cfg.worker.workDir}' - spark spark - -"
+        "d '${cfg.logDir}' - spark spark - -"
+      ];
+    };
+    users = {
+      users.spark = {
+        description = "spark user.";
+        group = "spark";
+        isSystemUser = true;
+      };
+      groups.spark = { };
+    };
+  };
+}
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 7becf6240710e..52249455fd45a 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -30,7 +30,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.boinc;
-        defaultText = "pkgs.boinc";
+        defaultText = literalExpression "pkgs.boinc";
         description = ''
           Which BOINC package to use.
         '';
@@ -60,7 +60,7 @@ in
       extraEnvPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = "[ pkgs.virtualbox ]";
+        example = literalExpression "[ pkgs.virtualbox ]";
         description = ''
           Additional packages to make available in the environment in which
           BOINC will run. Common choices are:
diff --git a/nixos/modules/services/computing/foldingathome/client.nix b/nixos/modules/services/computing/foldingathome/client.nix
index fbef6a04b16d0..aa9d0a5218fab 100644
--- a/nixos/modules/services/computing/foldingathome/client.nix
+++ b/nixos/modules/services/computing/foldingathome/client.nix
@@ -23,7 +23,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.fahclient;
-      defaultText = "pkgs.fahclient";
+      defaultText = literalExpression "pkgs.fahclient";
       description = ''
         Which Folding@home client to use.
       '';
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index a3dee94e2dc5d..0c96f32313297 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -132,8 +132,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.slurm.override { enableX11 = ! cfg.enableSrunX11; };
-        defaultText = "pkgs.slurm";
-        example = literalExample "pkgs.slurm-full";
+        defaultText = literalExpression "pkgs.slurm";
+        example = literalExpression "pkgs.slurm-full";
         description = ''
           The package to use for slurm binaries.
         '';
@@ -172,7 +172,7 @@ in
       nodeName = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExample ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
+        example = literalExpression ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
         description = ''
           Name that SLURM uses to refer to a node (or base partition for BlueGene
           systems). Typically this would be the string that "/bin/hostname -s"
@@ -183,7 +183,7 @@ in
       partitionName = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExample ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
+        example = literalExpression ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
         description = ''
           Name by which the partition may be referenced. Note that now you have
           to write the partition's parameters after the name.
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index f668e69e5df7b..2dc61c21ac71b 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -93,6 +93,7 @@ in {
         type = types.path;
         description = "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
         default = defaultMasterCfg;
+        defaultText = literalDocBook ''generated configuration file'';
         example = "/etc/nixos/buildbot/master.cfg";
       };
 
@@ -210,14 +211,14 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.python3Packages.buildbot-full;
-        defaultText = "pkgs.python3Packages.buildbot-full";
+        defaultText = literalExpression "pkgs.python3Packages.buildbot-full";
         description = "Package to use for buildbot.";
-        example = literalExample "pkgs.python3Packages.buildbot";
+        example = literalExpression "pkgs.python3Packages.buildbot";
       };
 
       packages = mkOption {
         default = [ pkgs.git ];
-        example = literalExample "[ pkgs.git ]";
+        defaultText = literalExpression "[ pkgs.git ]";
         type = types.listOf types.package;
         description = "Packages to add to PATH for the buildbot process.";
       };
@@ -225,9 +226,9 @@ in {
       pythonPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
         default = pythonPackages: with pythonPackages; [ ];
-        defaultText = "pythonPackages: with pythonPackages; [ ]";
+        defaultText = literalExpression "pythonPackages: with pythonPackages; [ ]";
         description = "Packages to add the to the PYTHONPATH of the buildbot process.";
-        example = literalExample "pythonPackages: with pythonPackages; [ requests ]";
+        example = literalExpression "pythonPackages: with pythonPackages; [ requests ]";
       };
     };
   };
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 708b3e1cc1825..dd4f4a4a74a9c 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -128,14 +128,14 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.python3Packages.buildbot-worker;
-        defaultText = "pkgs.python3Packages.buildbot-worker";
+        defaultText = literalExpression "pkgs.python3Packages.buildbot-worker";
         description = "Package to use for buildbot worker.";
-        example = literalExample "pkgs.python2Packages.buildbot-worker";
+        example = literalExpression "pkgs.python2Packages.buildbot-worker";
       };
 
       packages = mkOption {
         default = with pkgs; [ git ];
-        example = literalExample "[ pkgs.git ]";
+        defaultText = literalExpression "[ pkgs.git ]";
         type = types.listOf types.package;
         description = "Packages to add to PATH for the buildbot process.";
       };
diff --git a/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix
index b8982d757db91..1872567c9f127 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agents.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agents.nix
@@ -39,7 +39,7 @@ let
 
       package = mkOption {
         default = pkgs.buildkite-agent;
-        defaultText = "pkgs.buildkite-agent";
+        defaultText = literalExpression "pkgs.buildkite-agent";
         description = "Which buildkite-agent derivation to use";
         type = types.package;
       };
@@ -52,7 +52,7 @@ let
 
       runtimePackages = mkOption {
         default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
-        defaultText = "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
+        defaultText = literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
         description = "Add programs to the buildkite-agent environment";
         type = types.listOf types.package;
       };
@@ -168,7 +168,7 @@ let
       hooksPath = mkOption {
         type = types.path;
         default = hooksDir config;
-        defaultText = "generated from services.buildkite-agents.<name>.hooks";
+        defaultText = literalDocBook "generated from <option>services.buildkite-agents.&lt;name&gt;.hooks</option>";
         description = ''
           Path to the directory storing the hooks.
           Consider using <option>services.buildkite-agents.&lt;name&gt;.hooks.&lt;name&gt;</option>
@@ -179,6 +179,7 @@ let
       shell = mkOption {
         type = types.str;
         default = "${pkgs.bash}/bin/bash -e -c";
+        defaultText = literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
         description = ''
           Command that buildkite-agent 3 will execute when it spawns a shell.
         '';
diff --git a/nixos/modules/services/continuous-integration/github-runner.nix b/nixos/modules/services/continuous-integration/github-runner.nix
index f951c1553235c..943c1e4598df5 100644
--- a/nixos/modules/services/continuous-integration/github-runner.nix
+++ b/nixos/modules/services/continuous-integration/github-runner.nix
@@ -77,7 +77,7 @@ in
 
         Changing this option triggers a new runner registration.
       '';
-      example = literalExample ''[ "nixos" ]'';
+      example = literalExpression ''[ "nixos" ]'';
       default = [ ];
     };
 
@@ -105,6 +105,7 @@ in
         Which github-runner derivation to use.
       '';
       default = pkgs.github-runner;
+      defaultText = literalExpression "pkgs.github-runner";
     };
   };
 
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 15c37c2bc76d2..d4b8541c6a1bc 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -136,7 +136,7 @@ in
     checkInterval = mkOption {
       type = types.int;
       default = 0;
-      example = literalExample "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
+      example = literalExpression "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
       description = ''
         Defines the interval length, in seconds, between new jobs check.
         The default value is 3;
@@ -147,7 +147,7 @@ in
     concurrent = mkOption {
       type = types.int;
       default = 1;
-      example = literalExample "config.nix.maxJobs";
+      example = literalExpression "config.nix.maxJobs";
       description = ''
         Limits how many jobs globally can be run concurrently.
         The most upper limit of jobs using all defined runners.
@@ -203,7 +203,7 @@ in
         };
       };
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           listenAddress = "0.0.0.0:8093";
         }
@@ -234,8 +234,8 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.gitlab-runner;
-      defaultText = "pkgs.gitlab-runner";
-      example = literalExample "pkgs.gitlab-runner_1_11";
+      defaultText = literalExpression "pkgs.gitlab-runner";
+      example = literalExpression "pkgs.gitlab-runner_1_11";
       description = "Gitlab Runner package to use.";
     };
     extraPackages = mkOption {
@@ -248,7 +248,7 @@ in
     services = mkOption {
       description = "GitLab Runner services.";
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           # runner for building in docker via host's nix-daemon
           # nix store will be readable in runner, might be insecure
diff --git a/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
index 8cae08bf1fa02..acc3fb12484a5 100644
--- a/nixos/modules/services/continuous-integration/gocd-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -37,7 +37,7 @@ in {
 
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
-        defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
+        defaultText = literalExpression "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
         description = ''
           Packages to add to PATH for the Go.CD agent process.
diff --git a/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixos/modules/services/continuous-integration/gocd-server/default.nix
index 4c829664a0a5c..646bf13ac67ad 100644
--- a/nixos/modules/services/continuous-integration/gocd-server/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -69,7 +69,7 @@ in {
 
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
-        defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
+        defaultText = literalExpression "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
         description = ''
           Packages to add to PATH for the Go.CD server's process.
diff --git a/nixos/modules/services/continuous-integration/hail.nix b/nixos/modules/services/continuous-integration/hail.nix
index 5d0c3f7b4ab36..4070a3425c4f1 100644
--- a/nixos/modules/services/continuous-integration/hail.nix
+++ b/nixos/modules/services/continuous-integration/hail.nix
@@ -35,7 +35,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.haskellPackages.hail;
-      defaultText = "pkgs.haskellPackages.hail";
+      defaultText = literalExpression "pkgs.haskellPackages.hail";
       description = "Hail package to use.";
     };
   };
diff --git a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
index 70d85a97f3b7b..80c88714bfc17 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
@@ -1,16 +1,17 @@
 /*
 
-This file is for options that NixOS and nix-darwin have in common.
+  This file is for options that NixOS and nix-darwin have in common.
 
-Platform-specific code is in the respective default.nix files.
+  Platform-specific code is in the respective default.nix files.
 
- */
+*/
 
 { config, lib, options, pkgs, ... }:
 let
   inherit (lib)
     filterAttrs
-    literalExample
+    literalDocBook
+    literalExpression
     mkIf
     mkOption
     mkRemovedOptionModule
@@ -26,6 +27,16 @@ let
   settingsModule = { config, ... }: {
     freeformType = format.type;
     options = {
+      apiBaseUrl = mkOption {
+        description = ''
+          API base URL that the agent will connect to.
+
+          When using Hercules CI Enterprise, set this to the URL where your
+          Hercules CI server is reachable.
+        '';
+        type = types.str;
+        default = "https://hercules-ci.com";
+      };
       baseDirectory = mkOption {
         type = types.path;
         default = "/var/lib/hercules-ci-agent";
@@ -54,43 +65,88 @@ let
         type = types.either types.ints.positive (types.enum [ "auto" ]);
         default = "auto";
       };
+      labels = mkOption {
+        description = ''
+          A key-value map of user data.
+
+          This data will be available to organization members in the dashboard and API.
+
+          The values can be of any TOML type that corresponds to a JSON type, but arrays
+          can not contain tables/objects due to limitations of the TOML library. Values
+          involving arrays of non-primitive types may not be representable currently.
+        '';
+        type = format.type;
+        defaultText = literalExpression ''
+          {
+            agent.source = "..."; # One of "nixpkgs", "flake", "override"
+            lib.version = "...";
+            pkgs.version = "...";
+          }
+        '';
+      };
       workDirectory = mkOption {
         description = ''
           The directory in which temporary subdirectories are created for task state. This includes sources for Nix evaluation.
         '';
         type = types.path;
         default = config.baseDirectory + "/work";
-        defaultText = literalExample ''baseDirectory + "/work"'';
+        defaultText = literalExpression ''baseDirectory + "/work"'';
       };
       staticSecretsDirectory = mkOption {
         description = ''
           This is the default directory to look for statically configured secrets like <literal>cluster-join-token.key</literal>.
+
+          See also <literal>clusterJoinTokenPath</literal> and <literal>binaryCachesPath</literal> for fine-grained configuration.
         '';
         type = types.path;
         default = config.baseDirectory + "/secrets";
-        defaultText = literalExample ''baseDirectory + "/secrets"'';
+        defaultText = literalExpression ''baseDirectory + "/secrets"'';
       };
       clusterJoinTokenPath = mkOption {
         description = ''
           Location of the cluster-join-token.key file.
+
+          You can retrieve the contents of the file when creating a new agent via
+          <link xlink:href="https://hercules-ci.com/dashboard">https://hercules-ci.com/dashboard</link>.
+
+          As this value is confidential, it should not be in the store, but
+          installed using other means, such as agenix, NixOps
+          <literal>deployment.keys</literal>, or manual installation.
+
+          The contents of the file are used for authentication between the agent and the API.
         '';
         type = types.path;
         default = config.staticSecretsDirectory + "/cluster-join-token.key";
-        defaultText = literalExample ''staticSecretsDirectory + "/cluster-join-token.key"'';
-        # internal: It's a bit too detailed to show by default in the docs,
-        # but useful to define explicitly to allow reuse by other modules.
-        internal = true;
+        defaultText = literalExpression ''staticSecretsDirectory + "/cluster-join-token.key"'';
       };
       binaryCachesPath = mkOption {
         description = ''
-          Location of the binary-caches.json file.
+          Path to a JSON file containing binary cache secret keys.
+
+          As these values are confidential, they should not be in the store, but
+          copied over using other means, such as agenix, NixOps
+          <literal>deployment.keys</literal>, or manual installation.
+
+          The format is described on <link xlink:href="https://docs.hercules-ci.com/hercules-ci-agent/binary-caches-json/">https://docs.hercules-ci.com/hercules-ci-agent/binary-caches-json/</link>.
         '';
         type = types.path;
         default = config.staticSecretsDirectory + "/binary-caches.json";
-        defaultText = literalExample ''staticSecretsDirectory + "/binary-caches.json"'';
-        # internal: It's a bit too detailed to show by default in the docs,
-        # but useful to define explicitly to allow reuse by other modules.
-        internal = true;
+        defaultText = literalExpression ''staticSecretsDirectory + "/binary-caches.json"'';
+      };
+      secretsJsonPath = mkOption {
+        description = ''
+          Path to a JSON file containing secrets for effects.
+
+          As these values are confidential, they should not be in the store, but
+          copied over using other means, such as agenix, NixOps
+          <literal>deployment.keys</literal>, or manual installation.
+
+          The format is described on <link xlink:href="https://docs.hercules-ci.com/hercules-ci-agent/secrets-json/">https://docs.hercules-ci.com/hercules-ci-agent/secrets-json/</link>.
+
+        '';
+        type = types.path;
+        default = config.staticSecretsDirectory + "/secrets.json";
+        defaultText = literalExpression ''staticSecretsDirectory + "/secrets.json"'';
       };
     };
   };
@@ -158,7 +214,7 @@ in
       '';
       type = types.package;
       default = pkgs.hercules-ci-agent;
-      defaultText = literalExample "pkgs.hercules-ci-agent";
+      defaultText = literalExpression "pkgs.hercules-ci-agent";
     };
     settings = mkOption {
       description = ''
@@ -176,11 +232,11 @@ in
 
       These are written as options instead of let binding to allow sharing with
       default.nix on both NixOS and nix-darwin.
-     */
+    */
     tomlFile = mkOption {
       type = types.path;
       internal = true;
-      defaultText = "generated hercules-ci-agent.toml";
+      defaultText = literalDocBook "generated <literal>hercules-ci-agent.toml</literal>";
       description = ''
         The fully assembled config file.
       '';
diff --git a/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix b/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
index 06c174e7d376e..968bc8f1e54ed 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
@@ -1,10 +1,10 @@
 /*
 
-This file is for NixOS-specific options and configs.
+  This file is for NixOS-specific options and configs.
 
-Code that is shared with nix-darwin goes in common.nix.
+  Code that is shared with nix-darwin goes in common.nix.
 
- */
+*/
 
 { pkgs, config, lib, ... }:
 let
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 0103cd723d2ff..d6cde77c0a3f5 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -100,7 +100,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.hydra-unstable;
-        defaultText = "pkgs.hydra-unstable";
+        defaultText = literalExpression "pkgs.hydra-unstable";
         description = "The Hydra package.";
       };
 
@@ -155,7 +155,7 @@ in
       smtpHost = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = ["localhost"];
+        example = "localhost";
         description = ''
           Hostname of the SMTP server to use to send email.
         '';
diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix
index 98ef1e2c691b9..d37dcb5519d28 100644
--- a/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -81,14 +81,14 @@ in {
 
       package = mkOption {
         default = pkgs.jenkins;
-        defaultText = "pkgs.jenkins";
+        defaultText = literalExpression "pkgs.jenkins";
         type = types.package;
         description = "Jenkins package to use.";
       };
 
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.git pkgs.jdk11 config.programs.ssh.package pkgs.nix ];
-        defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk11 config.programs.ssh.package pkgs.nix ]";
+        defaultText = literalExpression "[ pkgs.stdenv pkgs.git pkgs.jdk11 config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
         description = ''
           Packages to add to PATH for the jenkins process.
@@ -120,7 +120,7 @@ in {
           <literal>null</literal>. You can generate this set with a
           tool such as <literal>jenkinsPlugins2nix</literal>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; }
         '';
       };
diff --git a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
index 536d394b3fd4e..3ca1542c18f2a 100644
--- a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -74,7 +74,7 @@ in {
       jsonJobs = mkOption {
         default = [ ];
         type = types.listOf types.str;
-        example = literalExample ''
+        example = literalExpression ''
           [
             '''
               [ { "job":
@@ -94,7 +94,7 @@ in {
       nixJobs = mkOption {
         default = [ ];
         type = types.listOf types.attrs;
-        example = literalExample ''
+        example = literalExpression ''
           [ { job =
               { name = "jenkins-job-test-3";
                 builders = [
diff --git a/nixos/modules/services/databases/aerospike.nix b/nixos/modules/services/databases/aerospike.nix
index 4b905f90529d2..8109762aea788 100644
--- a/nixos/modules/services/databases/aerospike.nix
+++ b/nixos/modules/services/databases/aerospike.nix
@@ -43,7 +43,7 @@ in
 
       package = mkOption {
         default = pkgs.aerospike;
-        defaultText = "pkgs.aerospike";
+        defaultText = literalExpression "pkgs.aerospike";
         type = types.package;
         description = "Which Aerospike derivation to use";
       };
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index 820be5085de9f..b36cac35e7c29 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -4,7 +4,8 @@ let
   inherit (lib)
     concatStringsSep
     flip
-    literalExample
+    literalDocBook
+    literalExpression
     optionalAttrs
     optionals
     recursiveUpdate
@@ -136,8 +137,8 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.cassandra;
-      defaultText = "pkgs.cassandra";
-      example = literalExample "pkgs.cassandra_3_11";
+      defaultText = literalExpression "pkgs.cassandra";
+      example = literalExpression "pkgs.cassandra_3_11";
       description = ''
         The Apache Cassandra package to use.
       '';
@@ -276,7 +277,7 @@ in
     extraEnvSh = mkOption {
       type = types.lines;
       default = "";
-      example = "CLASSPATH=$CLASSPATH:\${extraJar}";
+      example = literalExpression ''"CLASSPATH=$CLASSPATH:''${extraJar}"'';
       description = ''
         Extra shell lines to be appended onto cassandra-env.sh.
       '';
@@ -436,6 +437,7 @@ in
         if versionAtLeast cfg.package.version "3.11"
         then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
         else null;
+      defaultText = literalDocBook ''generated configuration file if version is at least 3.11, otherwise <literal>null</literal>'';
       example = "/var/lib/cassandra/jmx.password";
       description = ''
         Specify your own jmx roles file.
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
index 35fb46d69d8e5..eb061af926219 100644
--- a/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -150,7 +150,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.cockroachdb;
-        defaultText = "pkgs.cockroachdb";
+        defaultText = literalExpression "pkgs.cockroachdb";
         description = ''
           The CockroachDB derivation to use for running the service.
 
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 6cc29cd717ecb..16dd64f2373e6 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -44,8 +44,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.couchdb;
-        defaultText = "pkgs.couchdb";
-        example = literalExample "pkgs.couchdb";
+        defaultText = literalExpression "pkgs.couchdb";
         description = ''
           CouchDB package to use.
         '';
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index 0815487d4a1f2..4e3130bea22f8 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -44,11 +44,9 @@ in
 
       package = mkOption {
         default = pkgs.firebird;
-        defaultText = "pkgs.firebird";
+        defaultText = literalExpression "pkgs.firebird";
         type = types.package;
-        example = ''
-          <code>package = pkgs.firebird_3;</code>
-        '';
+        example = literalExpression "pkgs.firebird_3";
         description = ''
           Which Firebird package to be installed: <code>pkgs.firebird_3</code>
           For SuperServer use override: <code>pkgs.firebird_3.override { superServer = true; };</code>
@@ -56,7 +54,7 @@ in
       };
 
       port = mkOption {
-        default = "3050";
+        default = 3050;
         type = types.port;
         description = ''
           Port Firebird uses.
diff --git a/nixos/modules/services/databases/hbase.nix b/nixos/modules/services/databases/hbase.nix
index 2d1a47bbaa311..181be2d6b0b87 100644
--- a/nixos/modules/services/databases/hbase.nix
+++ b/nixos/modules/services/databases/hbase.nix
@@ -5,18 +5,27 @@ with lib;
 let
   cfg = config.services.hbase;
 
-  configFile = pkgs.writeText "hbase-site.xml" ''
-    <configuration>
-      <property>
-        <name>hbase.rootdir</name>
-        <value>file://${cfg.dataDir}/hbase</value>
-      </property>
-      <property>
-        <name>hbase.zookeeper.property.dataDir</name>
-        <value>${cfg.dataDir}/zookeeper</value>
-      </property>
-    </configuration>
-  '';
+  defaultConfig = {
+    "hbase.rootdir" = "file://${cfg.dataDir}/hbase";
+    "hbase.zookeeper.property.dataDir" = "${cfg.dataDir}/zookeeper";
+  };
+
+  buildProperty = configAttr:
+    (builtins.concatStringsSep "\n"
+      (lib.mapAttrsToList
+        (name: value: ''
+          <property>
+            <name>${name}</name>
+            <value>${builtins.toString value}</value>
+          </property>
+        '')
+        configAttr));
+
+  configFile = pkgs.writeText "hbase-site.xml"
+    ''<configuration>
+        ${buildProperty (defaultConfig // cfg.settings)}
+      </configuration>
+    '';
 
   configDir = pkgs.runCommand "hbase-config-dir" { preferLocalBuild = true; } ''
     mkdir -p $out
@@ -44,8 +53,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.hbase;
-        defaultText = "pkgs.hbase";
-        example = literalExample "pkgs.hbase";
+        defaultText = literalExpression "pkgs.hbase";
         description = ''
           HBase package to use.
         '';
@@ -86,6 +94,14 @@ in {
         '';
       };
 
+      settings = mkOption {
+        type = with lib.types; attrsOf (oneOf [ str int bool ]);
+        default = defaultConfig;
+        description = ''
+          configurations in hbase-site.xml, see <link xlink:href="https://github.com/apache/hbase/blob/master/hbase-server/src/test/resources/hbase-site.xml"/> for details.
+        '';
+      };
+
     };
 
   };
@@ -104,7 +120,8 @@ in {
       wantedBy = [ "multi-user.target" ];
 
       environment = {
-        JAVA_HOME = "${pkgs.jre}";
+        # JRE 15 removed option `UseConcMarkSweepGC` which is needed.
+        JAVA_HOME = "${pkgs.jre8}";
         HBASE_LOG_DIR = cfg.logDir;
       };
 
diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix
index dd5d69b1147a0..f7383b2023a4e 100644
--- a/nixos/modules/services/databases/influxdb.nix
+++ b/nixos/modules/services/databases/influxdb.nix
@@ -96,9 +96,8 @@ let
     };
   } cfg.extraConfig;
 
-  configFile = pkgs.runCommand "config.toml" {
-    buildInputs = [ pkgs.remarshal ];
-    preferLocalBuild = true;
+  configFile = pkgs.runCommandLocal "config.toml" {
+    nativeBuildInputs = [ pkgs.remarshal ];
   } ''
     remarshal -if json -of toml \
       < ${pkgs.writeText "config.json" (builtins.toJSON configOptions)} \
@@ -121,7 +120,7 @@ in
 
       package = mkOption {
         default = pkgs.influxdb;
-        defaultText = "pkgs.influxdb";
+        defaultText = literalExpression "pkgs.influxdb";
         description = "Which influxdb derivation to use";
         type = types.package;
       };
@@ -185,6 +184,7 @@ in
     users.users = optionalAttrs (cfg.user == "influxdb") {
       influxdb = {
         uid = config.ids.uids.influxdb;
+        group = "influxdb";
         description = "Influxdb daemon user";
       };
     };
diff --git a/nixos/modules/services/databases/influxdb2.nix b/nixos/modules/services/databases/influxdb2.nix
index df7bac4261b55..15f008cbc6d6c 100644
--- a/nixos/modules/services/databases/influxdb2.nix
+++ b/nixos/modules/services/databases/influxdb2.nix
@@ -11,13 +11,13 @@ in
       enable = mkEnableOption "the influxdb2 server";
       package = mkOption {
         default = pkgs.influxdb2;
-        defaultText = "pkgs.influxdb2";
+        defaultText = literalExpression "pkgs.influxdb2";
         description = "influxdb2 derivation to use.";
         type = types.package;
       };
       settings = mkOption {
         default = { };
-        description = "configuration options for influxdb2, see https://docs.influxdata.com/influxdb/v2.0/reference/config-options for details.";
+        description = ''configuration options for influxdb2, see <link xlink:href="https://docs.influxdata.com/influxdb/v2.0/reference/config-options"/> for details.'';
         type = format.type;
       };
     };
diff --git a/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix
index ca7b20eb049af..1c06937e2f30f 100644
--- a/nixos/modules/services/databases/memcached.nix
+++ b/nixos/modules/services/databases/memcached.nix
@@ -67,7 +67,9 @@ in
     users.users = optionalAttrs (cfg.user == "memcached") {
       memcached.description = "Memcached server user";
       memcached.isSystemUser = true;
+      memcached.group = "memcached";
     };
+    users.groups = optionalAttrs (cfg.user == "memcached") { memcached = {}; };
 
     environment.systemPackages = [ memcached ];
 
diff --git a/nixos/modules/services/databases/monetdb.nix b/nixos/modules/services/databases/monetdb.nix
index 5c66fc7b2e360..52a2ef041f8b5 100644
--- a/nixos/modules/services/databases/monetdb.nix
+++ b/nixos/modules/services/databases/monetdb.nix
@@ -17,7 +17,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.monetdb;
-        defaultText = "pkgs.monetdb";
+        defaultText = literalExpression "pkgs.monetdb";
         description = "MonetDB package to use.";
       };
 
diff --git a/nixos/modules/services/databases/mongodb.nix b/nixos/modules/services/databases/mongodb.nix
index db1e5fedf50d8..fccf85d482e07 100644
--- a/nixos/modules/services/databases/mongodb.nix
+++ b/nixos/modules/services/databases/mongodb.nix
@@ -33,7 +33,7 @@ in
 
       package = mkOption {
         default = pkgs.mongodb;
-        defaultText = "pkgs.mongodb";
+        defaultText = literalExpression "pkgs.mongodb";
         type = types.package;
         description = "
           Which MongoDB derivation to use.
@@ -123,9 +123,11 @@ in
 
     users.users.mongodb = mkIf (cfg.user == "mongodb")
       { name = "mongodb";
-        uid = config.ids.uids.mongodb;
+        isSystemUser = true;
+        group = "mongodb";
         description = "MongoDB server user";
       };
+    users.groups.mongodb = mkIf (cfg.user == "mongodb") {};
 
     environment.systemPackages = [ mongodb ];
 
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index b801b5cce635f..a9d9a6d80588e 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -34,7 +34,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.mariadb";
+        example = literalExpression "pkgs.mariadb";
         description = "
           Which MySQL derivation to use. MariaDB packages are supported too.
         ";
@@ -43,7 +43,7 @@ in
       bind = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = literalExample "0.0.0.0";
+        example = "0.0.0.0";
         description = "Address to bind to. The default is to bind to all addresses.";
       };
 
@@ -74,12 +74,12 @@ in
       configFile = mkOption {
         type = types.path;
         default = settingsFile;
-        defaultText = "settingsFile";
+        defaultText = literalExpression "settingsFile";
         description = ''
           Override the configuration file used by MySQL. By default,
           NixOS generates one automatically from <option>services.mysql.settings</option>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           pkgs.writeText "my.cnf" '''
             [mysqld]
             datadir = /var/lib/mysql
@@ -109,7 +109,7 @@ in
             </para>
           </note>
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             mysqld = {
               key_buffer_size = "6G";
@@ -167,7 +167,7 @@ in
           of MySQL. The schema attribute is optional: If not specified, an empty database is created.
         '';
         example = [
-          { name = "foodatabase"; schema = literalExample "./foodatabase.sql"; }
+          { name = "foodatabase"; schema = literalExpression "./foodatabase.sql"; }
           { name = "bardatabase"; }
         ];
       };
@@ -217,7 +217,7 @@ in
                 <link xlink:href="https://mariadb.com/kb/en/library/grant/">GRANT syntax</link>.
                 The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 {
                   "database.*" = "ALL PRIVILEGES";
                   "*.*" = "SELECT, LOCK TABLES";
@@ -235,7 +235,7 @@ in
           option is changed. This means that users created and permissions assigned once through this option or
           otherwise have to be removed manually.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           [
             {
               name = "nextcloud";
diff --git a/nixos/modules/services/databases/neo4j.nix b/nixos/modules/services/databases/neo4j.nix
index 53760bb24c4a4..f37e5ad16939b 100644
--- a/nixos/modules/services/databases/neo4j.nix
+++ b/nixos/modules/services/databases/neo4j.nix
@@ -179,7 +179,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.neo4j;
-      defaultText = "pkgs.neo4j";
+      defaultText = literalExpression "pkgs.neo4j";
       description = ''
         Neo4j package to use.
       '';
@@ -651,10 +651,12 @@ in {
       environment.systemPackages = [ cfg.package ];
 
       users.users.neo4j = {
-        uid = config.ids.uids.neo4j;
+        isSystemUser = true;
+        group = "neo4j";
         description = "Neo4j daemon user";
         home = cfg.directories.home;
       };
+      users.groups.neo4j = {};
     };
 
   meta = {
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index f0efc659cff74..2c1e25d430840 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -34,7 +34,7 @@ let
           in types.attrsOf (types.submodule { options = hiddenOptions; });
           default = {};
           description = "Child entries of the current entry, with recursively the same structure.";
-          example = lib.literalExample ''
+          example = lib.literalExpression ''
             {
                 "cn=schema" = {
                 # The attribute used in the DN must be defined
@@ -127,6 +127,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.openldap;
+        defaultText = literalExpression "pkgs.openldap";
         description = ''
           OpenLDAP package to use.
 
@@ -158,14 +159,14 @@ in {
       settings = mkOption {
         type = ldapAttrsType;
         description = "Configuration for OpenLDAP, in OLC format";
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           {
             attrs.olcLogLevel = [ "stats" ];
             children = {
               "cn=schema".includes = [
-                 "\${pkgs.openldap}/etc/schema/core.ldif"
-                 "\${pkgs.openldap}/etc/schema/cosine.ldif"
-                 "\${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+                 "''${pkgs.openldap}/etc/schema/core.ldif"
+                 "''${pkgs.openldap}/etc/schema/cosine.ldif"
+                 "''${pkgs.openldap}/etc/schema/inetorgperson.ldif"
               ];
               "olcDatabase={-1}frontend" = {
                 attrs = {
@@ -225,7 +226,7 @@ in {
           rebuilt on each server startup, so this will slow down server startup,
           especially with large databases.
         '';
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           {
             "dc=example,dc=org" = '''
               dn= dn: dc=example,dc=org
diff --git a/nixos/modules/services/databases/opentsdb.nix b/nixos/modules/services/databases/opentsdb.nix
index c4bd71f3d60e5..e873b2f701157 100644
--- a/nixos/modules/services/databases/opentsdb.nix
+++ b/nixos/modules/services/databases/opentsdb.nix
@@ -26,8 +26,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.opentsdb;
-        defaultText = "pkgs.opentsdb";
-        example = literalExample "pkgs.opentsdb";
+        defaultText = literalExpression "pkgs.opentsdb";
         description = ''
           OpenTSDB package to use.
         '';
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index 8508e76b5cd6e..f30f71866afd6 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -49,7 +49,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.pgmanage;
-      defaultText = "pkgs.pgmanage";
+      defaultText = literalExpression "pkgs.pgmanage";
       description = ''
         The pgmanage package to use.
       '';
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index fd4a195787f3a..d49cb4c51a729 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -44,7 +44,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.postgresql_11";
+        example = literalExpression "pkgs.postgresql_11";
         description = ''
           PostgreSQL package to use.
         '';
@@ -66,7 +66,7 @@ in
 
       dataDir = mkOption {
         type = types.path;
-        defaultText = "/var/lib/postgresql/\${config.services.postgresql.package.psqlSchema}";
+        defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.postgresql.package.psqlSchema}"'';
         example = "/var/lib/postgresql/11";
         description = ''
           The data directory for PostgreSQL. If left as the default value
@@ -161,7 +161,7 @@ in
                 <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>.
                 The attributes are used as <code>GRANT ''${attrValue} ON ''${attrName}</code>.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 {
                   "DATABASE \"nextcloud\"" = "ALL PRIVILEGES";
                   "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
@@ -179,7 +179,7 @@ in
           option is changed. This means that users created and permissions assigned once through this option or
           otherwise have to be removed manually.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           [
             {
               name = "nextcloud";
@@ -221,7 +221,7 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
+        example = literalExpression "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
         description = ''
           List of PostgreSQL plugins. PostgreSQL version for each plugin should
           match version for <literal>services.postgresql.package</literal> value.
@@ -241,7 +241,7 @@ in
             escaped with two single quotes as described by the upstream documentation linked above.
           </para></note>
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             log_connections = true;
             log_statement = "all";
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 8873f6d00e0b8..578d9d9ec8d78 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -47,7 +47,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.redis;
-        defaultText = "pkgs.redis";
+        defaultText = literalExpression "pkgs.redis";
         description = "Which Redis derivation to use.";
       };
 
@@ -133,7 +133,6 @@ in {
         type = with types; listOf (listOf int);
         default = [ [900 1] [300 10] [60 10000] ];
         description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
-        example = [ [900 1] [300 10] [60 10000] ];
       };
 
       slaveOf = mkOption {
@@ -217,7 +216,7 @@ in {
           <link xlink:href="https://redis.io/topics/config"/>
           for details on supported values.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
           }
@@ -246,6 +245,7 @@ in {
 
     users.users.redis = {
       description = "Redis database user";
+      group = "redis";
       isSystemUser = true;
     };
     users.groups.redis = {};
diff --git a/nixos/modules/services/databases/riak.nix b/nixos/modules/services/databases/riak.nix
index 657eeea87bf4c..cc4237d038cdb 100644
--- a/nixos/modules/services/databases/riak.nix
+++ b/nixos/modules/services/databases/riak.nix
@@ -21,8 +21,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.riak;
-        defaultText = "pkgs.riak";
-        example = literalExample "pkgs.riak";
+        defaultText = literalExpression "pkgs.riak";
         description = ''
           Riak package to use.
         '';
diff --git a/nixos/modules/services/databases/victoriametrics.nix b/nixos/modules/services/databases/victoriametrics.nix
index 9e2c79e61a394..0513dcff172b3 100644
--- a/nixos/modules/services/databases/victoriametrics.nix
+++ b/nixos/modules/services/databases/victoriametrics.nix
@@ -6,7 +6,7 @@ let cfg = config.services.victoriametrics; in
     package = mkOption {
       type = types.package;
       default = pkgs.victoriametrics;
-      defaultText = "pkgs.victoriametrics";
+      defaultText = literalExpression "pkgs.victoriametrics";
       description = ''
         The VictoriaMetrics distribution to use.
       '';
diff --git a/nixos/modules/services/desktops/cpupower-gui.nix b/nixos/modules/services/desktops/cpupower-gui.nix
new file mode 100644
index 0000000000000..f66afc0a3dc17
--- /dev/null
+++ b/nixos/modules/services/desktops/cpupower-gui.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cpupower-gui;
+in {
+  options = {
+    services.cpupower-gui = {
+      enable = mkOption {
+        type = lib.types.bool;
+        default = false;
+        example = true;
+        description = ''
+          Enables dbus/systemd service needed by cpupower-gui.
+          These services are responsible for retrieving and modifying cpu power
+          saving settings.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.cpupower-gui ];
+    services.dbus.packages = [ pkgs.cpupower-gui ];
+    systemd.user = {
+      services.cpupower-gui-user = {
+        description = "Apply cpupower-gui config at user login";
+        wantedBy = [ "graphical-session.target" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkgs.cpupower-gui}/bin/cpupower-gui config";
+        };
+      };
+    };
+    systemd.services = {
+      cpupower-gui = {
+        description = "Apply cpupower-gui config at boot";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkgs.cpupower-gui}/bin/cpupower-gui config";
+        };
+      };
+      cpupower-gui-helper = {
+        description = "cpupower-gui system helper";
+        aliases = [ "dbus-org.rnd2.cpupower_gui.helper.service" ];
+        serviceConfig = {
+          Type = "dbus";
+          BusName = "org.rnd2.cpupower_gui.helper";
+          ExecStart = "${pkgs.cpupower-gui}/lib/cpupower-gui/cpupower-gui-helper";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index cb5c948ecf78d..60a34dd656313 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -21,7 +21,6 @@ let
 
       isAllowed = mkOption {
         type = types.bool;
-        default = null;
         description = ''
           Whether the application will be allowed access to location information.
         '';
@@ -29,7 +28,6 @@ let
 
       isSystem = mkOption {
         type = types.bool;
-        default = null;
         description = ''
           Whether the application is a system component or not.
         '';
@@ -162,7 +160,7 @@ in
       appConfig = mkOption {
         type = types.attrsOf appConfigModule;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           "com.github.app" = {
             isAllowed = true;
             isSystem = true;
diff --git a/nixos/modules/services/desktops/gnome/evolution-data-server.nix b/nixos/modules/services/desktops/gnome/evolution-data-server.nix
index ef5ad797c2781..bd2242d98182e 100644
--- a/nixos/modules/services/desktops/gnome/evolution-data-server.nix
+++ b/nixos/modules/services/desktops/gnome/evolution-data-server.nix
@@ -39,7 +39,7 @@ with lib;
       plugins = mkOption {
         type = types.listOf types.package;
         default = [ ];
-        example = literalExample "[ pkgs.evolution-ews ]";
+        example = literalExpression "[ pkgs.evolution-ews ]";
         description = "Plugins for Evolution.";
       };
 
diff --git a/nixos/modules/services/desktops/gnome/gnome-keyring.nix b/nixos/modules/services/desktops/gnome/gnome-keyring.nix
index cda44bab8bfaa..d821da164beb1 100644
--- a/nixos/modules/services/desktops/gnome/gnome-keyring.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-keyring.nix
@@ -52,8 +52,10 @@ with lib;
     security.pam.services.login.enableGnomeKeyring = true;
 
     security.wrappers.gnome-keyring-daemon = {
-      source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon";
+      owner = "root";
+      group = "root";
       capabilities = "cap_ipc_lock=ep";
+      source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon";
     };
 
   };
diff --git a/nixos/modules/services/desktops/gsignond.nix b/nixos/modules/services/desktops/gsignond.nix
index 5ab9add9f32d5..465acd73fa64d 100644
--- a/nixos/modules/services/desktops/gsignond.nix
+++ b/nixos/modules/services/desktops/gsignond.nix
@@ -9,7 +9,7 @@ let
 in
 {
 
-  meta.maintainers = pkgs.pantheon.maintainers;
+  meta.maintainers = teams.pantheon.members;
 
   ###### interface
 
diff --git a/nixos/modules/services/desktops/gvfs.nix b/nixos/modules/services/desktops/gvfs.nix
index 966a4d38662bd..b6a27279bdf8e 100644
--- a/nixos/modules/services/desktops/gvfs.nix
+++ b/nixos/modules/services/desktops/gvfs.nix
@@ -35,6 +35,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.gnome.gvfs;
+        defaultText = literalExpression "pkgs.gnome.gvfs";
         description = "Which GVfs package to use.";
       };
 
diff --git a/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json b/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json
deleted file mode 100644
index 46697ece44835..0000000000000
--- a/nixos/modules/services/desktops/pipewire/bluez-hardware.conf.json
+++ /dev/null
@@ -1,243 +0,0 @@
-{
-  "bluez5.features.device": [
-    {
-      "name": "Air 1 Plus",
-      "no-features": [
-        "hw-volume-mic"
-      ]
-    },
-    {
-      "name": "AirPods",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "name": "AirPods Pro",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "name": "AXLOIE Goin",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "name": "BAA 100",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "name": "JBL Endurance RUN BT",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl",
-        "sbc-xq"
-      ]
-    },
-    {
-      "name": "JBL LIVE650BTNC"
-    },
-    {
-      "name": "Soundcore Life P2-L",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "name": "Urbanista Stockholm Plus",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "address": "~^94:16:25:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^9c:64:8b:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^a0:e9:db:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^0c:a6:94:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^00:14:02:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^44:5e:f3:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^d4:9c:28:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^00:18:6b:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^b8:ad:3e:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^a0:e9:db:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^00:24:1c:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^00:11:b1:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^a4:15:66:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^00:14:f1:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^00:26:7e:",
-      "no-features": [
-        "hw-volume"
-      ]
-    },
-    {
-      "address": "~^90:03:b7:",
-      "no-features": [
-        "hw-volume"
-      ]
-    }
-  ],
-  "bluez5.features.adapter": [
-    {
-      "bus-type": "usb",
-      "vendor-id": "usb:0bda"
-    },
-    {
-      "bus-type": "usb",
-      "no-features": [
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "no-features": [
-        "msbc-alt1-rtl"
-      ]
-    }
-  ],
-  "bluez5.features.kernel": [
-    {
-      "sysname": "Linux",
-      "release": "~^[0-4]\\.",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.[1-7]\\.",
-      "no-features": [
-        "msbc-alt1",
-        "msbc-alt1-rtl"
-      ]
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.(8|9|10)\\.",
-      "no-features": [
-        "msbc-alt1"
-      ]
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.10\\.(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50)($|[^0-9])"
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.10\\.",
-      "no-features": [
-        "msbc-alt1"
-      ]
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.12\\.(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17)($|[^0-9])"
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.12\\.",
-      "no-features": [
-        "msbc-alt1"
-      ]
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.13\\.(1|2)($|[^0-9])"
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.13\\.",
-      "no-features": [
-        "msbc-alt1"
-      ]
-    },
-    {
-      "sysname": "Linux",
-      "release": "~^5\\.14\\.",
-      "no-features": [
-        "msbc-alt1"
-      ]
-    },
-    {
-      "no-features": []
-    }
-  ]
-}
diff --git a/nixos/modules/services/desktops/pipewire/client-rt.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
index 284d8c394a611..284d8c394a611 100644
--- a/nixos/modules/services/desktops/pipewire/client-rt.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/client.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
index 71294a0e78a2d..71294a0e78a2d 100644
--- a/nixos/modules/services/desktops/pipewire/client.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/jack.conf.json b/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
index 128178bfa027f..128178bfa027f 100644
--- a/nixos/modules/services/desktops/pipewire/jack.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
index 3ed994f111458..3ed994f111458 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
index a923ab4db2357..a923ab4db2357 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
index 53fc9cc96343b..53fc9cc96343b 100644
--- a/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json
+++ b/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
index 6d1c23e825699..6d1c23e825699 100644
--- a/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json
+++ b/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/media-session.conf.json b/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
index 4b4e302af3876..4b4e302af3876 100644
--- a/nixos/modules/services/desktops/pipewire/media-session.conf.json
+++ b/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
index b08cba1b604b5..b08cba1b604b5 100644
--- a/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json
+++ b/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
index 41ab995e32925..4be3e881a9dc3 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
@@ -13,17 +13,15 @@ let
   # Use upstream config files passed through spa-json-dump as the base
   # Patched here as necessary for them to work with this module
   defaults = {
-    alsa-monitor = (builtins.fromJSON (builtins.readFile ./alsa-monitor.conf.json));
-    bluez-monitor = (builtins.fromJSON (builtins.readFile ./bluez-monitor.conf.json));
-    bluez-hardware = (builtins.fromJSON (builtins.readFile ./bluez-hardware.conf.json));
-    media-session = (builtins.fromJSON (builtins.readFile ./media-session.conf.json));
-    v4l2-monitor = (builtins.fromJSON (builtins.readFile ./v4l2-monitor.conf.json));
+    alsa-monitor = lib.importJSON ./media-session/alsa-monitor.conf.json;
+    bluez-monitor = lib.importJSON ./media-session/bluez-monitor.conf.json;
+    media-session = lib.importJSON ./media-session/media-session.conf.json;
+    v4l2-monitor = lib.importJSON ./media-session/v4l2-monitor.conf.json;
   };
 
   configs = {
     alsa-monitor = recursiveUpdate defaults.alsa-monitor cfg.config.alsa-monitor;
     bluez-monitor = recursiveUpdate defaults.bluez-monitor cfg.config.bluez-monitor;
-    bluez-hardware = defaults.bluez-hardware;
     media-session = recursiveUpdate defaults.media-session cfg.config.media-session;
     v4l2-monitor = recursiveUpdate defaults.v4l2-monitor cfg.config.v4l2-monitor;
   };
@@ -39,14 +37,14 @@ in {
       enable = mkOption {
         type = types.bool;
         default = config.services.pipewire.enable;
-        defaultText = "config.services.pipewire.enable";
+        defaultText = literalExpression "config.services.pipewire.enable";
         description = "Example pipewire session manager";
       };
 
       package = mkOption {
         type = types.package;
-        default = pkgs.pipewire.mediaSession;
-        example = literalExample "pkgs.pipewire.mediaSession";
+        default = pkgs.pipewire-media-session;
+        defaultText = literalExpression "pkgs.pipewire-media-session";
         description = ''
           The pipewire-media-session derivation to use.
         '';
@@ -57,7 +55,7 @@ in {
           type = json.type;
           description = ''
             Configuration for the media session core. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf
+            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf
           '';
           default = {};
         };
@@ -66,7 +64,7 @@ in {
           type = json.type;
           description = ''
             Configuration for the alsa monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf
+            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf
           '';
           default = {};
         };
@@ -75,7 +73,7 @@ in {
           type = json.type;
           description = ''
             Configuration for the bluez5 monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf
+            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf
           '';
           default = {};
         };
@@ -84,7 +82,7 @@ in {
           type = json.type;
           description = ''
             Configuration for the V4L2 monitor. For details see
-            https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf
+            https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf
           '';
           default = {};
         };
@@ -122,10 +120,6 @@ in {
       mkIf config.services.pipewire.pulse.enable {
         source = json.generate "bluez-monitor.conf" configs.bluez-monitor;
       };
-    environment.etc."pipewire/media-session.d/bluez-hardware.conf" =
-      mkIf config.services.pipewire.pulse.enable {
-        source = json.generate "bluez-hardware.conf" configs.bluez-hardware;
-      };
 
     environment.etc."pipewire/media-session.d/with-jack" =
       mkIf config.services.pipewire.jack.enable {
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index bc75aa2717a96..55755ecd64577 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -22,12 +22,11 @@ let
   # Use upstream config files passed through spa-json-dump as the base
   # Patched here as necessary for them to work with this module
   defaults = {
-    client = builtins.fromJSON (builtins.readFile ./client.conf.json);
-    client-rt = builtins.fromJSON (builtins.readFile ./client-rt.conf.json);
-    jack = builtins.fromJSON (builtins.readFile ./jack.conf.json);
-    # Remove session manager invocation from the upstream generated file, it points to the wrong path
-    pipewire = builtins.fromJSON (builtins.readFile ./pipewire.conf.json);
-    pipewire-pulse = builtins.fromJSON (builtins.readFile ./pipewire-pulse.conf.json);
+    client = lib.importJSON ./daemon/client.conf.json;
+    client-rt = lib.importJSON ./daemon/client-rt.conf.json;
+    jack = lib.importJSON ./daemon/jack.conf.json;
+    pipewire = lib.importJSON ./daemon/pipewire.conf.json;
+    pipewire-pulse = lib.importJSON ./daemon/pipewire-pulse.conf.json;
   };
 
   configs = {
@@ -51,8 +50,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.pipewire;
-        defaultText = "pkgs.pipewire";
-        example = literalExample "pkgs.pipewire";
+        defaultText = literalExpression "pkgs.pipewire";
         description = ''
           The pipewire derivation to use.
         '';
diff --git a/nixos/modules/services/development/blackfire.nix b/nixos/modules/services/development/blackfire.nix
index 6fd948cce38d3..8564aabc6a37d 100644
--- a/nixos/modules/services/development/blackfire.nix
+++ b/nixos/modules/services/development/blackfire.nix
@@ -19,7 +19,7 @@ in {
       enable = lib.mkEnableOption "Blackfire profiler agent";
       settings = lib.mkOption {
         description = ''
-          See https://blackfire.io/docs/configuration/agent
+          See https://blackfire.io/docs/up-and-running/configuration/agent
         '';
         type = lib.types.submodule {
           freeformType = with lib.types; attrsOf str;
@@ -53,13 +53,8 @@ in {
 
     services.blackfire-agent.settings.socket = "unix:///run/${agentSock}";
 
-    systemd.services.blackfire-agent = {
-      description = "Blackfire agent";
-
-      serviceConfig = {
-        ExecStart = "${pkgs.blackfire}/bin/blackfire-agent";
-        RuntimeDirectory = "blackfire";
-      };
-    };
+    systemd.packages = [
+      pkgs.blackfire
+    ];
   };
 }
diff --git a/nixos/modules/services/development/blackfire.xml b/nixos/modules/services/development/blackfire.xml
index ad4af35788db9..cecd249dda480 100644
--- a/nixos/modules/services/development/blackfire.xml
+++ b/nixos/modules/services/development/blackfire.xml
@@ -28,13 +28,14 @@ in {
     enable = true;
     settings = {
       # You will need to get credentials at https://blackfire.io/my/settings/credentials
-      # You can also use other options described in https://blackfire.io/docs/configuration/agent
+      # You can also use other options described in https://blackfire.io/docs/up-and-running/configuration/agent
       server-id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
       server-token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
     };
   };
 
   # Make the agent run on start-up.
+  # (WantedBy= from the upstream unit not respected: https://github.com/NixOS/nixpkgs/issues/81138)
   # Alternately, you can start it manually with `systemctl start blackfire-agent`.
   systemd.services.blackfire-agent.wantedBy = [ "phpfpm-foo.service" ];
 }</programlisting>
diff --git a/nixos/modules/services/development/distccd.nix b/nixos/modules/services/development/distccd.nix
index 8790ea08d0c18..9f6d5c813c458 100644
--- a/nixos/modules/services/development/distccd.nix
+++ b/nixos/modules/services/development/distccd.nix
@@ -69,7 +69,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.distcc;
-        example = "pkgs.distcc";
+        defaultText = literalExpression "pkgs.distcc";
         description = ''
           The distcc package to use.
         '';
diff --git a/nixos/modules/services/development/hoogle.nix b/nixos/modules/services/development/hoogle.nix
index a6693013b73c0..7c635f7a5b8d7 100644
--- a/nixos/modules/services/development/hoogle.nix
+++ b/nixos/modules/services/development/hoogle.nix
@@ -27,8 +27,8 @@ in {
     packages = mkOption {
       type = types.functionTo (types.listOf types.package);
       default = hp: [];
-      defaultText = "hp: []";
-      example = "hp: with hp; [ text lens ]";
+      defaultText = literalExpression "hp: []";
+      example = literalExpression "hp: with hp; [ text lens ]";
       description = ''
         The Haskell packages to generate documentation for.
 
@@ -41,7 +41,7 @@ in {
     haskellPackages = mkOption {
       description = "Which haskell package set to use.";
       default = pkgs.haskellPackages;
-      defaultText = "pkgs.haskellPackages";
+      defaultText = literalExpression "pkgs.haskellPackages";
     };
 
     home = mkOption {
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index 21b84b3bcdaa8..bebb3c3f13f01 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -40,6 +40,7 @@ in {
       # want to pass in JUPYTER_PATH but use .environment instead,
       # saving a rebuild.
       default = pkgs.python3.pkgs.notebook;
+      defaultText = literalExpression "pkgs.python3.pkgs.notebook";
       description = ''
         Jupyter package to use.
       '';
@@ -105,10 +106,7 @@ in {
           "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
         It will be interpreted at the end of the notebookConfig.
       '';
-      example = [
-        "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'"
-        "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
-      ];
+      example = "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'";
     };
 
     notebookConfig = mkOption {
@@ -125,7 +123,7 @@ in {
       })));
 
       default = null;
-      example = literalExample ''
+      example = literalExpression ''
         {
           python3 = let
             env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
diff --git a/nixos/modules/services/development/jupyter/kernel-options.nix b/nixos/modules/services/development/jupyter/kernel-options.nix
index 03547637449a2..348a8b44b382b 100644
--- a/nixos/modules/services/development/jupyter/kernel-options.nix
+++ b/nixos/modules/services/development/jupyter/kernel-options.nix
@@ -9,10 +9,10 @@ with lib;
     displayName = mkOption {
       type = types.str;
       default = "";
-      example = [
+      example = literalExpression ''
         "Python 3"
         "Python 3 for Data Science"
-      ];
+      '';
       description = ''
         Name that will be shown to the user.
       '';
@@ -43,7 +43,7 @@ with lib;
     logo32 = mkOption {
       type = types.nullOr types.path;
       default = null;
-      example = "{env.sitePackages}/ipykernel/resources/logo-32x32.png";
+      example = literalExpression ''"''${env.sitePackages}/ipykernel/resources/logo-32x32.png"'';
       description = ''
         Path to 32x32 logo png.
       '';
@@ -51,7 +51,7 @@ with lib;
     logo64 = mkOption {
       type = types.nullOr types.path;
       default = null;
-      example = "{env.sitePackages}/ipykernel/resources/logo-64x64.png";
+      example = literalExpression ''"''${env.sitePackages}/ipykernel/resources/logo-64x64.png"'';
       description = ''
         Path to 64x64 logo png.
       '';
diff --git a/nixos/modules/services/development/jupyterhub/default.nix b/nixos/modules/services/development/jupyterhub/default.nix
index a1df4468cfff6..fa6b3be960ab3 100644
--- a/nixos/modules/services/development/jupyterhub/default.nix
+++ b/nixos/modules/services/development/jupyterhub/default.nix
@@ -66,18 +66,24 @@ in {
         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
+      example = ''
+        c.SystemdSpawner.mem_limit = '8G'
+        c.SystemdSpawner.cpu_limit = 2.0
       '';
     };
 
     jupyterhubEnv = mkOption {
       type = types.package;
-      default = (pkgs.python3.withPackages (p: with p; [
+      default = pkgs.python3.withPackages (p: with p; [
         jupyterhub
         jupyterhub-systemdspawner
-      ]));
+      ]);
+      defaultText = literalExpression ''
+        pkgs.python3.withPackages (p: with p; [
+          jupyterhub
+          jupyterhub-systemdspawner
+        ])
+      '';
       description = ''
         Python environment to run jupyterhub
 
@@ -90,10 +96,16 @@ in {
 
     jupyterlabEnv = mkOption {
       type = types.package;
-      default = (pkgs.python3.withPackages (p: with p; [
+      default = pkgs.python3.withPackages (p: with p; [
         jupyterhub
         jupyterlab
-      ]));
+      ]);
+      defaultText = literalExpression ''
+        pkgs.python3.withPackages (p: with p; [
+          jupyterhub
+          jupyterlab
+        ])
+      '';
       description = ''
         Python environment to run jupyterlab
 
@@ -111,7 +123,7 @@ in {
       })));
 
       default = null;
-      example = literalExample ''
+      example = literalExpression ''
         {
           python3 = let
             env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix
index fc576e4c18ba8..bda63518bfd95 100644
--- a/nixos/modules/services/development/lorri.nix
+++ b/nixos/modules/services/development/lorri.nix
@@ -21,8 +21,7 @@ in {
         description = ''
           The lorri package to use.
         '';
-        defaultText = lib.literalExample "pkgs.lorri";
-        example = lib.literalExample "pkgs.lorri";
+        defaultText = lib.literalExpression "pkgs.lorri";
       };
     };
   };
diff --git a/nixos/modules/services/display-managers/greetd.nix b/nixos/modules/services/display-managers/greetd.nix
index c3072bf09964c..895961707d363 100644
--- a/nixos/modules/services/display-managers/greetd.nix
+++ b/nixos/modules/services/display-managers/greetd.nix
@@ -13,13 +13,13 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.greetd.greetd;
-      defaultText = "pkgs.greetd.greetd";
+      defaultText = literalExpression "pkgs.greetd.greetd";
       description = "The greetd package that should be used.";
     };
 
     settings = mkOption {
       type = settingsFormat.type;
-      example = literalExample ''
+      example = literalExpression ''
         {
           default_session = {
             command = "''${pkgs.greetd.greetd}/bin/agreety --cmd sway";
@@ -43,7 +43,7 @@ in
     restart = mkOption {
       type = types.bool;
       default = !(cfg.settings ? initial_session);
-      defaultText = "!(config.services.greetd.settings ? initial_session)";
+      defaultText = literalExpression "!(config.services.greetd.settings ? initial_session)";
       description = ''
         Wether to restart greetd when it terminates (e.g. on failure).
         This is usually desirable so a user can always log in, but should be disabled when using 'settings.initial_session' (autologin),
@@ -99,7 +99,12 @@ in
 
     systemd.defaultUnit = "graphical.target";
 
-    users.users.greeter.isSystemUser = true;
+    users.users.greeter = {
+      isSystemUser = true;
+      group = "greeter";
+    };
+
+    users.groups.greeter = {};
   };
 
   meta.maintainers = with maintainers; [ queezle ];
diff --git a/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index 00d9eaad9eb9f..e2bbd27f6e560 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/nixos/modules/services/editors/emacs.nix
@@ -66,7 +66,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.emacs;
-      defaultText = "pkgs.emacs";
+      defaultText = literalExpression "pkgs.emacs";
       description = ''
         emacs derivation to use.
       '';
diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix
index 3eb0753194dd7..16fe52a232bd4 100644
--- a/nixos/modules/services/editors/infinoted.nix
+++ b/nixos/modules/services/editors/infinoted.nix
@@ -11,7 +11,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.libinfinity;
-      defaultText = "pkgs.libinfinity";
+      defaultText = literalExpression "pkgs.libinfinity";
       description = ''
         Package providing infinoted
       '';
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
new file mode 100644
index 0000000000000..422ee95100742
--- /dev/null
+++ b/nixos/modules/services/finance/odoo.nix
@@ -0,0 +1,122 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.odoo;
+  format = pkgs.formats.ini {};
+in
+{
+  options = {
+    services.odoo = {
+      enable = mkEnableOption "odoo";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.odoo;
+        defaultText = literalExpression "pkgs.odoo";
+        description = "Odoo package to use.";
+      };
+
+      addons = mkOption {
+        type = with types; listOf package;
+        default = [];
+        example = literalExpression "[ pkgs.odoo_enterprise ]";
+        description = "Odoo addons.";
+      };
+
+      settings = mkOption {
+        type = format.type;
+        default = {};
+        description = ''
+          Odoo configuration settings. For more details see <link xlink:href="https://www.odoo.com/documentation/15.0/administration/install/deploy.html"/>
+        '';
+      };
+
+      domain = mkOption {
+        type = with types; nullOr str;
+        description = "Domain to host Odoo with nginx";
+        default = null;
+      };
+    };
+  };
+
+  config = mkIf (cfg.enable) (let
+    cfgFile = format.generate "odoo.cfg" cfg.settings;
+  in {
+    services.nginx = mkIf (cfg.domain != null) {
+      upstreams = {
+        odoo.servers = {
+          "127.0.0.1:8069" = {};
+        };
+
+        odoochat.servers = {
+          "127.0.0.1:8072" = {};
+        };
+      };
+
+      virtualHosts."${cfg.domain}" = {
+        extraConfig = ''
+          proxy_read_timeout 720s;
+          proxy_connect_timeout 720s;
+          proxy_send_timeout 720s;
+
+          proxy_set_header X-Forwarded-Host $host;
+          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+          proxy_set_header X-Forwarded-Proto $scheme;
+          proxy_set_header X-Real-IP $remote_addr;
+        '';
+
+        locations = {
+          "/longpolling" = {
+            proxyPass = "http://odoochat";
+          };
+
+          "/" = {
+            proxyPass = "http://odoo";
+            extraConfig = ''
+              proxy_redirect off;
+            '';
+          };
+        };
+      };
+    };
+
+    services.odoo.settings.options = {
+      proxy_mode = cfg.domain != null;
+    };
+
+    users.users.odoo = {
+      isSystemUser = true;
+      group = "odoo";
+    };
+    users.groups.odoo = {};
+
+    systemd.services.odoo = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "postgresql.service" ];
+
+      # pg_dump
+      path = [ config.services.postgresql.package ];
+
+      requires = [ "postgresql.service" ];
+      script = "HOME=$STATE_DIRECTORY ${cfg.package}/bin/odoo ${optionalString (cfg.addons != []) "--addons-path=${concatMapStringsSep "," escapeShellArg cfg.addons}"} -c ${cfgFile}";
+
+      serviceConfig = {
+        DynamicUser = true;
+        User = "odoo";
+        StateDirectory = "odoo";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+
+      ensureUsers = [{
+        name = "odoo";
+        ensurePermissions = { "DATABASE odoo" = "ALL PRIVILEGES"; };
+      }];
+      ensureDatabases = [ "odoo" ];
+    };
+  });
+}
diff --git a/nixos/modules/services/games/crossfire-server.nix b/nixos/modules/services/games/crossfire-server.nix
new file mode 100644
index 0000000000000..a33025e0c3e13
--- /dev/null
+++ b/nixos/modules/services/games/crossfire-server.nix
@@ -0,0 +1,179 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.crossfire-server;
+  serverPort = 13327;
+in {
+  options.services.crossfire-server = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled, the Crossfire game server will be started at boot.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.crossfire-server;
+      defaultText = literalExpression "pkgs.crossfire-server";
+      description = ''
+        The package to use for the Crossfire server (and map/arch data, if you
+        don't change dataDir).
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "${cfg.package}/share/crossfire";
+      defaultText = literalExpression ''"''${config.services.crossfire.package}/share/crossfire"'';
+      description = ''
+        Where to load readonly data from -- maps, archetypes, treasure tables,
+        and the like. If you plan to edit the data on the live server (rather
+        than overlaying the crossfire-maps and crossfire-arch packages and
+        nixos-rebuilding), point this somewhere read-write and copy the data
+        there before starting the server.
+      '';
+    };
+
+    stateDir = mkOption {
+      type = types.str;
+      default = "/var/lib/crossfire";
+      description = ''
+        Where to store runtime data (save files, persistent items, etc).
+
+        If left at the default, this will be automatically created on server
+        startup if it does not already exist. If changed, it is the admin's
+        responsibility to make sure that the directory exists and is writeable
+        by the `crossfire` user.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to open ports in the firewall for the server.
+      '';
+    };
+
+    configFiles = mkOption {
+      type = types.attrsOf types.str;
+      description = ''
+        Text to append to the corresponding configuration files. Note that the
+        files given in the example are *not* the complete set of files available
+        to customize; look in /etc/crossfire after enabling the server to see
+        the available files, and read the comments in each file for detailed
+        documentation on the format and what settings are available.
+
+        Note that the motd, rules, and news files, if configured here, will
+        overwrite the example files that come with the server, rather than being
+        appended to them as the other configuration files are.
+      '';
+      example = literalExpression ''
+        {
+          dm_file = '''
+            admin:secret_password:localhost
+            jane:xyzzy:*
+          ''';
+          ban_file = '''
+            # Bob is a jerk
+            bob@*
+            # So is everyone on 192.168.86.255/24
+            *@192.168.86.
+          ''';
+          metaserver2 = '''
+            metaserver2_notification on
+            localhostname crossfire.example.net
+          ''';
+          motd = "Welcome to CrossFire!";
+          news = "No news yet.";
+          rules = "Don't be a jerk.";
+          settings = '''
+            # be nicer to newbies and harsher to experienced players
+            balanced_stat_loss true
+            # don't let players pick up and use admin-created items
+            real_wiz false
+          ''';
+        }
+      '';
+      default = {};
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.crossfire = {
+      description     = "Crossfire server daemon user";
+      home            = cfg.stateDir;
+      createHome      = false;
+      isSystemUser    = true;
+      group           = "crossfire";
+    };
+    users.groups.crossfire = {};
+
+    # Merge the cfg.configFiles setting with the default files shipped with
+    # Crossfire.
+    # For most files this consists of reading ${crossfire}/etc/crossfire/${name}
+    # and appending the user setting to it; the motd, news, and rules are handled
+    # specially, with user-provided values completely replacing the original.
+    environment.etc = lib.attrsets.mapAttrs'
+      (name: value: lib.attrsets.nameValuePair "crossfire/${name}" {
+        mode = "0644";
+        text =
+          (optionalString (!elem name ["motd" "news" "rules"])
+            (fileContents "${cfg.package}/etc/crossfire/${name}"))
+          + "\n${value}";
+      }) ({
+        ban_file = "";
+        dm_file = "";
+        exp_table = "";
+        forbid = "";
+        metaserver2 = "";
+        motd = (fileContents "${cfg.package}/etc/crossfire/motd");
+        news = (fileContents "${cfg.package}/etc/crossfire/news");
+        rules = (fileContents "${cfg.package}/etc/crossfire/rules");
+        settings = "";
+        stat_bonus = "";
+      } // cfg.configFiles);
+
+    systemd.services.crossfire-server = {
+      description   = "Crossfire Server Daemon";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      serviceConfig = mkMerge [
+        {
+          ExecStart = "${cfg.package}/bin/crossfire-server -conf /etc/crossfire -local '${cfg.stateDir}' -data '${cfg.dataDir}'";
+          Restart = "always";
+          User = "crossfire";
+          Group = "crossfire";
+          WorkingDirectory = cfg.stateDir;
+        }
+        (mkIf (cfg.stateDir == "/var/lib/crossfire") {
+          StateDirectory = "crossfire";
+        })
+      ];
+
+      # The crossfire server needs access to a bunch of files at runtime that
+      # are not created automatically at server startup; they're meant to be
+      # installed in $PREFIX/var/crossfire by `make install`. And those files
+      # need to be writeable, so we can't just point at the ones in the nix
+      # store. Instead we take the approach of copying them out of the store
+      # on first run. If `bookarch` already exists, we assume the rest of the
+      # files do as well, and copy nothing -- otherwise we risk ovewriting
+      # server state information every time the server is upgraded.
+      preStart = ''
+        if [ ! -e "${cfg.stateDir}"/bookarch ]; then
+          ${pkgs.rsync}/bin/rsync -a --chmod=u=rwX,go=rX \
+            "${cfg.package}/var/crossfire/" "${cfg.stateDir}/"
+        fi
+      '';
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ serverPort ];
+    };
+  };
+}
diff --git a/nixos/modules/services/games/deliantra-server.nix b/nixos/modules/services/games/deliantra-server.nix
new file mode 100644
index 0000000000000..b7011f4c35428
--- /dev/null
+++ b/nixos/modules/services/games/deliantra-server.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.deliantra-server;
+  serverPort = 13327;
+in {
+  options.services.deliantra-server = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled, the Deliantra game server will be started at boot.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.deliantra-server;
+      defaultText = literalExpression "pkgs.deliantra-server";
+      description = ''
+        The package to use for the Deliantra server (and map/arch data, if you
+        don't change dataDir).
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "${pkgs.deliantra-data}";
+      defaultText = literalExpression ''"''${pkgs.deliantra-data}"'';
+      description = ''
+        Where to store readonly data (maps, archetypes, sprites, etc).
+        Note that if you plan to use the live map editor (rather than editing
+        the maps offline and then nixos-rebuilding), THIS MUST BE WRITEABLE --
+        copy the deliantra-data someplace writeable (say,
+        /var/lib/deliantra/data) and update this option accordingly.
+      '';
+    };
+
+    stateDir = mkOption {
+      type = types.str;
+      default = "/var/lib/deliantra";
+      description = ''
+        Where to store runtime data (save files, persistent items, etc).
+
+        If left at the default, this will be automatically created on server
+        startup if it does not already exist. If changed, it is the admin's
+        responsibility to make sure that the directory exists and is writeable
+        by the `crossfire` user.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to open ports in the firewall for the server.
+      '';
+    };
+
+    configFiles = mkOption {
+      type = types.attrsOf types.str;
+      description = ''
+        Contents of the server configuration files. These will be appended to
+        the example configurations the server comes with and overwrite any
+        default settings defined therein.
+
+        The example here is not comprehensive. See the files in
+        /etc/deliantra-server after enabling this module for full documentation.
+      '';
+      example = literalExpression ''
+        {
+          dm_file = '''
+            admin:secret_password:localhost
+            jane:xyzzy:*
+          ''';
+          motd = "Welcome to Deliantra!";
+          settings = '''
+            # Settings for game mechanics.
+            stat_loss_on_death true
+            armor_max_enchant 7
+          ''';
+          config = '''
+            # Settings for the server daemon.
+            hiscore_url https://deliantra.example.net/scores/
+            max_map_reset 86400
+          ''';
+        }
+      '';
+      default = {
+        motd = "";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.deliantra = {
+      description     = "Deliantra server daemon user";
+      home            = cfg.stateDir;
+      createHome      = false;
+      isSystemUser    = true;
+      group           = "deliantra";
+    };
+    users.groups.deliantra = {};
+
+    # Merge the cfg.configFiles setting with the default files shipped with
+    # Deliantra.
+    # For most files this consists of reading
+    # ${deliantra}/etc/deliantra-server/${name} and appending the user setting
+    # to it.
+    environment.etc = lib.attrsets.mapAttrs'
+      (name: value: lib.attrsets.nameValuePair "deliantra-server/${name}" {
+        mode = "0644";
+        text =
+          # Deliantra doesn't come with a motd file, but respects it if present
+          # in /etc.
+          (optionalString (name != "motd")
+            (fileContents "${cfg.package}/etc/deliantra-server/${name}"))
+          + "\n${value}";
+      }) ({
+        motd = "";
+        settings = "";
+        config = "";
+        dm_file = "";
+      } // cfg.configFiles);
+
+    systemd.services.deliantra-server = {
+      description   = "Deliantra Server Daemon";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      environment = {
+        DELIANTRA_DATADIR="${cfg.dataDir}";
+        DELIANTRA_LOCALDIR="${cfg.stateDir}";
+        DELIANTRA_CONFDIR="/etc/deliantra-server";
+      };
+
+      serviceConfig = mkMerge [
+        {
+          ExecStart = "${cfg.package}/bin/deliantra-server";
+          Restart = "always";
+          User = "deliantra";
+          Group = "deliantra";
+          WorkingDirectory = cfg.stateDir;
+        }
+        (mkIf (cfg.stateDir == "/var/lib/deliantra") {
+          StateDirectory = "deliantra";
+        })
+      ];
+
+      # The deliantra server needs access to a bunch of files at runtime that
+      # are not created automatically at server startup; they're meant to be
+      # installed in $PREFIX/var/deliantra-server by `make install`. And those
+      # files need to be writeable, so we can't just point at the ones in the
+      # nix store. Instead we take the approach of copying them out of the store
+      # on first run. If `bookarch` already exists, we assume the rest of the
+      # files do as well, and copy nothing -- otherwise we risk ovewriting
+      # server state information every time the server is upgraded.
+      preStart = ''
+        if [ ! -e "${cfg.stateDir}"/bookarch ]; then
+          ${pkgs.rsync}/bin/rsync -a --chmod=u=rwX,go=rX \
+            "${cfg.package}/var/deliantra-server/" "${cfg.stateDir}/"
+        fi
+      '';
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ serverPort ];
+    };
+  };
+}
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index 3cb1427579273..0e8860a02819c 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -86,7 +86,7 @@ in
       configFile = mkOption {
         type = types.path;
         default = configFile;
-        defaultText = "configFile";
+        defaultText = literalExpression "configFile";
         description = ''
           The server's configuration file.
 
@@ -162,8 +162,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.factorio-headless;
-        defaultText = "pkgs.factorio-headless";
-        example = "pkgs.factorio-headless-experimental";
+        defaultText = literalExpression "pkgs.factorio-headless";
+        example = literalExpression "pkgs.factorio-headless-experimental";
         description = ''
           Factorio version to use. This defaults to the stable channel.
         '';
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index eb9288fca586c..ddbe9508a4dcf 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -109,7 +109,7 @@ in {
           You can use <link xlink:href="https://mcuuid.net/"/> to get a
           Minecraft UUID for a username.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
             username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
@@ -120,7 +120,7 @@ in {
       serverProperties = mkOption {
         type = with types; attrsOf (oneOf [ bool int str ]);
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             server-port = 43000;
             difficulty = 3;
@@ -144,8 +144,8 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.minecraft-server;
-        defaultText = "pkgs.minecraft-server";
-        example = literalExample "pkgs.minecraft-server_1_12_2";
+        defaultText = literalExpression "pkgs.minecraft-server";
+        example = literalExpression "pkgs.minecraft-server_1_12_2";
         description = "Version of minecraft-server to run.";
       };
 
@@ -167,8 +167,10 @@ in {
       description     = "Minecraft server service user";
       home            = cfg.dataDir;
       createHome      = true;
-      uid             = config.ids.uids.minecraft;
+      isSystemUser    = true;
+      group           = "minecraft";
     };
+    users.groups.minecraft = {};
 
     systemd.services.minecraft-server = {
       description   = "Minecraft Server Service";
diff --git a/nixos/modules/services/hardware/acpid.nix b/nixos/modules/services/hardware/acpid.nix
index 3e619fe32ef17..883ef08300372 100644
--- a/nixos/modules/services/hardware/acpid.nix
+++ b/nixos/modules/services/hardware/acpid.nix
@@ -61,7 +61,7 @@ in
           options = {
             event = mkOption {
               type = types.str;
-              example = [ "button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*" ];
+              example = literalExpression ''"button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*"'';
               description = "Event type.";
             };
 
diff --git a/nixos/modules/services/hardware/actkbd.nix b/nixos/modules/services/hardware/actkbd.nix
index f7770f85da335..b499de97b2c3c 100644
--- a/nixos/modules/services/hardware/actkbd.nix
+++ b/nixos/modules/services/hardware/actkbd.nix
@@ -74,7 +74,7 @@ in
       bindings = mkOption {
         type = types.listOf (types.submodule bindingCfg);
         default = [];
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           [ { keys = [ 113 ]; events = [ "key" ]; command = "''${pkgs.alsa-utils}/bin/amixer -q set Master toggle"; }
           ]
         '';
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index 08ad90126b1d2..7f75ac272d405 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -6,7 +6,7 @@ let
   inherit (lib)
     mkDefault mkEnableOption mkIf mkOption
     mkRenamedOptionModule mkRemovedOptionModule
-    concatStringsSep escapeShellArgs
+    concatStringsSep escapeShellArgs literalExpression
     optional optionals optionalAttrs recursiveUpdate types;
 
   cfgFmt = pkgs.formats.ini { };
@@ -53,8 +53,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.bluez;
-        defaultText = "pkgs.bluez";
-        example = "pkgs.bluezFull";
+        defaultText = literalExpression "pkgs.bluez";
+        example = literalExpression "pkgs.bluezFull";
         description = ''
           Which BlueZ package to use.
 
diff --git a/nixos/modules/services/hardware/fancontrol.nix b/nixos/modules/services/hardware/fancontrol.nix
index 5574c5a132e59..861b70970b879 100644
--- a/nixos/modules/services/hardware/fancontrol.nix
+++ b/nixos/modules/services/hardware/fancontrol.nix
@@ -38,6 +38,7 @@ in
       after = [ "lm_sensors.service" ];
 
       serviceConfig = {
+        Restart = "on-failure";
         ExecStart = "${pkgs.lm_sensors}/sbin/fancontrol ${configFile}";
       };
     };
diff --git a/nixos/modules/services/hardware/freefall.nix b/nixos/modules/services/hardware/freefall.nix
index 83f1e8c84f283..3f7b159244960 100644
--- a/nixos/modules/services/hardware/freefall.nix
+++ b/nixos/modules/services/hardware/freefall.nix
@@ -21,7 +21,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.freefall;
-      defaultText = "pkgs.freefall";
+      defaultText = literalExpression "pkgs.freefall";
       description = ''
         freefall derivation to use.
       '';
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 51eca19dca32b..e0506416ffa3f 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -80,7 +80,7 @@ in {
       extraTrustedKeys = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ /etc/nixos/fwupd/myfirmware.pem ]";
+        example = literalExpression "[ /etc/nixos/fwupd/myfirmware.pem ]";
         description = ''
           Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
         '';
@@ -98,6 +98,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.fwupd;
+        defaultText = literalExpression "pkgs.fwupd";
         description = ''
           Which fwupd package to use.
         '';
diff --git a/nixos/modules/services/hardware/interception-tools.nix b/nixos/modules/services/hardware/interception-tools.nix
index fadcb19a016fc..e69c05841ee01 100644
--- a/nixos/modules/services/hardware/interception-tools.nix
+++ b/nixos/modules/services/hardware/interception-tools.nix
@@ -15,6 +15,7 @@ in {
     plugins = mkOption {
       type = types.listOf types.package;
       default = [ pkgs.interception-tools-plugins.caps2esc ];
+      defaultText = literalExpression "[ pkgs.interception-tools-plugins.caps2esc ]";
       description = ''
         A list of interception tools plugins that will be made available to use
         inside the udevmon configuration.
diff --git a/nixos/modules/services/hardware/joycond.nix b/nixos/modules/services/hardware/joycond.nix
new file mode 100644
index 0000000000000..ffef4f8a4e188
--- /dev/null
+++ b/nixos/modules/services/hardware/joycond.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.joycond;
+  kernelPackages = config.boot.kernelPackages;
+in
+
+with lib;
+
+{
+  options.services.joycond = {
+    enable = mkEnableOption "support for Nintendo Pro Controllers and Joycons";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.joycond;
+      defaultText = "pkgs.joycond";
+      description = ''
+        The joycond package to use.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      kernelPackages.hid-nintendo
+      cfg.package
+    ];
+
+    boot.extraModulePackages = [ kernelPackages.hid-nintendo ];
+    boot.kernelModules = [ "hid_nintendo" ];
+
+    services.udev.packages = [ cfg.package ];
+
+    systemd.packages = [ cfg.package ];
+
+    # Workaround for https://github.com/NixOS/nixpkgs/issues/81138
+    systemd.services.joycond.wantedBy = [ "multi-user.target" ];
+  };
+}
diff --git a/nixos/modules/services/hardware/lirc.nix b/nixos/modules/services/hardware/lirc.nix
index 826e512c75d17..f970b0a095c35 100644
--- a/nixos/modules/services/hardware/lirc.nix
+++ b/nixos/modules/services/hardware/lirc.nix
@@ -65,7 +65,7 @@ in {
       unitConfig.Documentation = [ "man:lircd(8)" ];
 
       serviceConfig = {
-        RuntimeDirectory = "lirc";
+        RuntimeDirectory = ["lirc" "lirc/lock"];
 
         # Service runtime directory and socket share same folder.
         # Following hacks are necessary to get everything right:
diff --git a/nixos/modules/services/hardware/pcscd.nix b/nixos/modules/services/hardware/pcscd.nix
index 4fc1e351f5037..b1a5c680a022e 100644
--- a/nixos/modules/services/hardware/pcscd.nix
+++ b/nixos/modules/services/hardware/pcscd.nix
@@ -21,8 +21,8 @@ in
     plugins = mkOption {
       type = types.listOf types.package;
       default = [ pkgs.ccid ];
-      defaultText = "[ pkgs.ccid ]";
-      example = literalExample "[ pkgs.pcsc-cyberjack ]";
+      defaultText = literalExpression "[ pkgs.ccid ]";
+      example = literalExpression "[ pkgs.pcsc-cyberjack ]";
       description = "Plugin packages to be used for PCSC-Lite.";
     };
 
diff --git a/nixos/modules/services/hardware/power-profiles-daemon.nix b/nixos/modules/services/hardware/power-profiles-daemon.nix
index 70b7a72b8bae0..4144bc6670885 100644
--- a/nixos/modules/services/hardware/power-profiles-daemon.nix
+++ b/nixos/modules/services/hardware/power-profiles-daemon.nix
@@ -42,6 +42,8 @@ in
       }
     ];
 
+    environment.systemPackages = [ package ];
+
     services.dbus.packages = [ package ];
 
     services.udev.packages = [ package ];
diff --git a/nixos/modules/services/hardware/rasdaemon.nix b/nixos/modules/services/hardware/rasdaemon.nix
new file mode 100644
index 0000000000000..b1efe0f18c88b
--- /dev/null
+++ b/nixos/modules/services/hardware/rasdaemon.nix
@@ -0,0 +1,171 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.rasdaemon;
+
+in
+{
+  options.hardware.rasdaemon = {
+
+    enable = mkEnableOption "RAS logging daemon";
+
+    record = mkOption {
+      type = types.bool;
+      default = true;
+      description = "record events via sqlite3, required for ras-mc-ctl";
+    };
+
+    mainboard = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Custom mainboard description, see <citerefentry><refentrytitle>ras-mc-ctl</refentrytitle><manvolnum>8</manvolnum></citerefentry> for more details.";
+      example = ''
+        vendor = ASRock
+        model = B450M Pro4
+
+        # it should default to such values from
+        # /sys/class/dmi/id/board_[vendor|name]
+        # alternatively one can supply a script
+        # that returns the same format as above
+
+        script = <path to script>
+      '';
+    };
+
+    # TODO, accept `rasdaemon.labels = " ";` or `rasdaemon.labels = { dell = " "; asrock = " "; };'
+
+    labels = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Additional memory module label descriptions to be placed in /etc/ras/dimm_labels.d/labels";
+      example = ''
+        # vendor and model may be shown by 'ras-mc-ctl --mainboard'
+        vendor: ASRock
+          product: To Be Filled By O.E.M.
+          model: B450M Pro4
+            # these labels are names for the motherboard slots
+            # the numbers may be shown by `ras-mc-ctl --error-count`
+            # they are mc:csrow:channel
+            DDR4_A1: 0.2.0;  DDR4_B1: 0.2.1;
+            DDR4_A2: 0.3.0;  DDR4_B2: 0.3.1;
+      '';
+    };
+
+    config = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        rasdaemon configuration, currently only used for CE PFA
+        for details, read rasdaemon.outPath/etc/sysconfig/rasdaemon's comments
+      '';
+      example = ''
+        # defaults from included config
+        PAGE_CE_REFRESH_CYCLE="24h"
+        PAGE_CE_THRESHOLD="50"
+        PAGE_CE_ACTION="soft"
+      '';
+    };
+
+    extraModules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = "extra kernel modules to load";
+      example = [ "i7core_edac" ];
+    };
+
+    testing = mkEnableOption "error injection infrastructure";
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.etc = {
+      "ras/mainboard" = {
+        enable = cfg.mainboard != "";
+        text = cfg.mainboard;
+      };
+    # TODO, handle multiple cfg.labels.brand = " ";
+      "ras/dimm_labels.d/labels" = {
+        enable = cfg.labels != "";
+        text = cfg.labels;
+      };
+      "sysconfig/rasdaemon" = {
+        enable = cfg.config != "";
+        text = cfg.config;
+      };
+    };
+    environment.systemPackages = [ pkgs.rasdaemon ]
+      ++ optionals (cfg.testing) (with pkgs.error-inject; [
+        edac-inject
+        mce-inject
+        aer-inject
+      ]);
+
+    boot.initrd.kernelModules = cfg.extraModules
+      ++ optionals (cfg.testing) [
+        # edac_core and amd64_edac should get loaded automatically
+        # i7core_edac may not be, and may not be required, but should load successfully
+        "edac_core"
+        "amd64_edac"
+        "i7core_edac"
+        "mce-inject"
+        "aer-inject"
+      ];
+
+    boot.kernelPatches = optionals (cfg.testing) [{
+      name = "rasdaemon-tests";
+      patch = null;
+      extraConfig = ''
+        EDAC_DEBUG y
+        X86_MCE_INJECT y
+
+        PCIEPORTBUS y
+        PCIEAER y
+        PCIEAER_INJECT y
+      '';
+    }];
+
+    # i tried to set up a group for this
+    # but rasdaemon needs higher permissions?
+    # `rasdaemon: Can't locate a mounted debugfs`
+
+    # most of this taken from src/misc/
+    systemd.services = {
+      rasdaemon = {
+        description = "the RAS logging daemon";
+        documentation = [ "man:rasdaemon(1)" ];
+        wantedBy = [ "multi-user.target" ];
+        after = [ "syslog.target" ];
+
+        serviceConfig = {
+          StateDirectory = optionalString (cfg.record) "rasdaemon";
+
+          ExecStart = "${pkgs.rasdaemon}/bin/rasdaemon --foreground"
+            + optionalString (cfg.record) " --record";
+          ExecStop = "${pkgs.rasdaemon}/bin/rasdaemon --disable";
+          Restart = "on-abort";
+
+          # src/misc/rasdaemon.service.in shows this:
+          # ExecStartPost = ${pkgs.rasdaemon}/bin/rasdaemon --enable
+          # but that results in unpredictable existence of the database
+          # and everything seems to be enabled without this...
+        };
+      };
+      ras-mc-ctl = mkIf (cfg.labels != "") {
+        description = "register DIMM labels on startup";
+        documentation = [ "man:ras-mc-ctl(8)" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkgs.rasdaemon}/bin/ras-mc-ctl --register-labels";
+          RemainAfterExit = true;
+        };
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.evils ];
+
+}
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index ccf726bd182bb..caf232e234eb5 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/nixos/modules/services/hardware/sane.nix
@@ -73,7 +73,7 @@ in
           The example contains the package for HP scanners.
         </para></note>
       '';
-      example = literalExample "[ pkgs.hplipWithPlugin ]";
+      example = literalExpression "[ pkgs.hplipWithPlugin ]";
     };
 
     hardware.sane.disabledDefaultBackends = mkOption {
@@ -115,6 +115,7 @@ in
     hardware.sane.drivers.scanSnap.package = mkOption {
       type = types.package;
       default = pkgs.sane-drivers.epjitsu;
+      defaultText = literalExpression "pkgs.sane-drivers.epjitsu";
       description = ''
         Epjitsu driver package to use. Useful if you want to extract the driver files yourself.
 
diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
index a6afa01dd8124..8f9998108406b 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
@@ -20,7 +20,7 @@ let
           the name of attribute will be used.
         '';
 
-        example = literalExample "office1";
+        example = "office1";
       };
 
       model = mkOption {
@@ -29,7 +29,7 @@ let
           The model of the network device.
         '';
 
-        example = literalExample "MFC-7860DW";
+        example = "MFC-7860DW";
       };
 
       ip = mkOption {
@@ -40,7 +40,7 @@ let
           provide a nodename.
         '';
 
-        example = literalExample "192.168.1.2";
+        example = "192.168.1.2";
       };
 
       nodename = mkOption {
@@ -51,7 +51,7 @@ let
           provide an ip.
         '';
 
-        example = literalExample "BRW0080927AFBCE";
+        example = "BRW0080927AFBCE";
       };
 
     };
diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
index 89b5ff0e0282d..2e4ad8cc3ba0d 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
@@ -20,7 +20,7 @@ let
           the name of attribute will be used.
         '';
 
-        example = literalExample "office1";
+        example = "office1";
       };
 
       model = mkOption {
@@ -29,7 +29,7 @@ let
           The model of the network device.
         '';
 
-        example = literalExample "ADS-1200";
+        example = "ADS-1200";
       };
 
       ip = mkOption {
@@ -40,7 +40,7 @@ let
           provide a nodename.
         '';
 
-        example = literalExample "192.168.1.2";
+        example = "192.168.1.2";
       };
 
       nodename = mkOption {
@@ -51,7 +51,7 @@ let
           provide an ip.
         '';
 
-        example = literalExample "BRW0080927AFBCE";
+        example = "BRW0080927AFBCE";
       };
 
     };
diff --git a/nixos/modules/services/hardware/tcsd.nix b/nixos/modules/services/hardware/tcsd.nix
index 0d36bce357ba0..c549a67750136 100644
--- a/nixos/modules/services/hardware/tcsd.nix
+++ b/nixos/modules/services/hardware/tcsd.nix
@@ -149,12 +149,10 @@ in
     users.users = optionalAttrs (cfg.user == "tss") {
       tss = {
         group = "tss";
-        uid = config.ids.uids.tss;
+        isSystemUser = true;
       };
     };
 
-    users.groups = optionalAttrs (cfg.group == "tss") {
-      tss.gid = config.ids.gids.tss;
-    };
+    users.groups = optionalAttrs (cfg.group == "tss") { tss = {}; };
   };
 }
diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix
index aa936ac09d1d1..3b495d00df071 100644
--- a/nixos/modules/services/hardware/thermald.nix
+++ b/nixos/modules/services/hardware/thermald.nix
@@ -27,7 +27,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.thermald;
-        defaultText = "pkgs.thermald";
+        defaultText = literalExpression "pkgs.thermald";
         description = "Which thermald package to use.";
       };
     };
diff --git a/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
index f9f5234bdc3f2..4e979c4d8fa14 100644
--- a/nixos/modules/services/hardware/triggerhappy.nix
+++ b/nixos/modules/services/hardware/triggerhappy.nix
@@ -69,7 +69,7 @@ in
       bindings = mkOption {
         type = types.listOf (types.submodule bindingCfg);
         default = [];
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           [ { keys = ["PLAYPAUSE"];  cmd = "''${pkgs.mpc_cli}/bin/mpc -q toggle"; } ]
         '';
         description = ''
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index 9c2f78a755ddd..212c0227c0d0a 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -50,7 +50,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.undervolt;
-      defaultText = "pkgs.undervolt";
+      defaultText = literalExpression "pkgs.undervolt";
       description = ''
         undervolt derivation to use.
       '';
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
index 449810b53150f..92c060147bfc0 100644
--- a/nixos/modules/services/hardware/upower.nix
+++ b/nixos/modules/services/hardware/upower.nix
@@ -30,8 +30,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.upower;
-        defaultText = "pkgs.upower";
-        example = lib.literalExample "pkgs.upower";
+        defaultText = literalExpression "pkgs.upower";
         description = ''
           Which upower package to use.
         '';
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
index 8a6cde51b06ff..5ec222b805c84 100644
--- a/nixos/modules/services/hardware/vdr.nix
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -17,8 +17,8 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.vdr;
-        defaultText = "pkgs.vdr";
-        example = literalExample "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
+        defaultText = literalExpression "pkgs.vdr";
+        example = literalExpression "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
         description = "Package to use.";
       };
 
diff --git a/nixos/modules/services/logging/SystemdJournal2Gelf.nix b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
index f26aef7262ba1..f28ecab8ac237 100644
--- a/nixos/modules/services/logging/SystemdJournal2Gelf.nix
+++ b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
@@ -36,6 +36,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.systemd-journal2gelf;
+        defaultText = literalExpression "pkgs.systemd-journal2gelf";
         description = ''
           SystemdJournal2Gelf package to use.
         '';
diff --git a/nixos/modules/services/logging/awstats.nix b/nixos/modules/services/logging/awstats.nix
index 896f52302ff3f..df0124380ff0d 100644
--- a/nixos/modules/services/logging/awstats.nix
+++ b/nixos/modules/services/logging/awstats.nix
@@ -51,7 +51,7 @@ let
       hostAliases = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = "[ \"www.example.org\" ]";
+        example = [ "www.example.org" ];
         description = ''
           List of aliases the site has.
         '';
@@ -60,12 +60,12 @@ let
       extraConfig = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "ValidHTTPCodes" = "404";
           }
         '';
-        description = "Extra configuration to be appendend to awstats.\${name}.conf.";
+        description = "Extra configuration to be appended to awstats.\${name}.conf.";
       };
 
       webService = {
@@ -106,7 +106,7 @@ in
     configs = mkOption {
       type = types.attrsOf (types.submodule configOpts);
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "mysite" = {
             domain = "example.com";
diff --git a/nixos/modules/services/logging/fluentd.nix b/nixos/modules/services/logging/fluentd.nix
index 95825705d9d71..dd19617a13ffc 100644
--- a/nixos/modules/services/logging/fluentd.nix
+++ b/nixos/modules/services/logging/fluentd.nix
@@ -27,7 +27,7 @@ in {
       package = mkOption {
         type = types.path;
         default = pkgs.fluentd;
-        defaultText = "pkgs.fluentd";
+        defaultText = literalExpression "pkgs.fluentd";
         description = "The fluentd package to use.";
       };
 
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index af70d27fcf99e..e6a23233ba28d 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -38,14 +38,13 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.graylog;
-        defaultText = "pkgs.graylog";
+        defaultText = literalExpression "pkgs.graylog";
         description = "Graylog package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "graylog";
-        example = literalExample "graylog";
         description = "User account under which graylog runs";
       };
 
@@ -90,7 +89,7 @@ in
 
       elasticsearchHosts = mkOption {
         type = types.listOf types.str;
-        example = literalExample ''[ "http://node1:9200" "http://user:password@node2:19200" ]'';
+        example = literalExpression ''[ "http://node1:9200" "http://user:password@node2:19200" ]'';
         description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
       };
 
@@ -128,10 +127,12 @@ in
 
     users.users = mkIf (cfg.user == "graylog") {
       graylog = {
-        uid = config.ids.uids.graylog;
+        isSystemUser = true;
+        group = "graylog";
         description = "Graylog server daemon user";
       };
     };
+    users.groups = mkIf (cfg.user == "graylog") {};
 
     systemd.tmpfiles.rules = [
       "d '${cfg.messageJournalDir}' - ${cfg.user} - - -"
diff --git a/nixos/modules/services/logging/journalbeat.nix b/nixos/modules/services/logging/journalbeat.nix
index 89f53b1b24546..2d98598c1bee0 100644
--- a/nixos/modules/services/logging/journalbeat.nix
+++ b/nixos/modules/services/logging/journalbeat.nix
@@ -27,8 +27,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.journalbeat;
-        defaultText = "pkgs.journalbeat";
-        example = literalExample "pkgs.journalbeat7";
+        defaultText = literalExpression "pkgs.journalbeat";
+        example = literalExpression "pkgs.journalbeat7";
         description = ''
           The journalbeat package to use
         '';
diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix
index 4296b2270c29f..c8738b734f9a1 100644
--- a/nixos/modules/services/logging/logcheck.nix
+++ b/nixos/modules/services/logging/logcheck.nix
@@ -172,7 +172,7 @@ in
 
       extraRulesDirs = mkOption {
         default = [];
-        example = "/etc/logcheck";
+        example = [ "/etc/logcheck" ];
         type = types.listOf types.path;
         description = ''
           Directories with extra rules.
@@ -215,12 +215,16 @@ in
 
     users.users = optionalAttrs (cfg.user == "logcheck") {
       logcheck = {
-        uid = config.ids.uids.logcheck;
+        group = "logcheck";
+        isSystemUser = true;
         shell = "/bin/sh";
         description = "Logcheck user account";
         extraGroups = cfg.extraGroups;
       };
     };
+    users.groups = optionalAttrs (cfg.user == "logcheck") {
+      logcheck = {};
+    };
 
     system.activationScripts.logcheck = ''
       mkdir -m 700 -p /var/{lib,lock}/logcheck
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 7d6102b82557e..624b6cfb12159 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -111,7 +111,7 @@ in
           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 ''
+        example = literalExpression ''
           {
             httpd = {
               path = "/var/log/httpd/*.log";
diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index 7a2f5681612cd..a08203dffe789 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -23,12 +23,16 @@ let
 
   logstashSettingsYml = pkgs.writeText "logstash.yml" cfg.extraSettings;
 
+  logstashJvmOptionsFile = pkgs.writeText "jvm.options" cfg.extraJvmOptions;
+
   logstashSettingsDir = pkgs.runCommand "logstash-settings" {
+      inherit logstashJvmOptionsFile;
       inherit logstashSettingsYml;
       preferLocalBuild = true;
     } ''
     mkdir -p $out
     ln -s $logstashSettingsYml $out/logstash.yml
+    ln -s $logstashJvmOptionsFile $out/jvm.options
   '';
 in
 
@@ -53,15 +57,14 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.logstash;
-        defaultText = "pkgs.logstash";
-        example = literalExample "pkgs.logstash";
+        defaultText = literalExpression "pkgs.logstash";
         description = "Logstash package to use.";
       };
 
       plugins = mkOption {
         type = types.listOf types.path;
         default = [ ];
-        example = literalExample "[ pkgs.logstash-contrib ]";
+        example = literalExpression "[ pkgs.logstash-contrib ]";
         description = "The paths to find other logstash plugins in.";
       };
 
@@ -102,12 +105,14 @@ in
         type = types.lines;
         default = "generator { }";
         description = "Logstash input configuration.";
-        example = ''
-          # Read from journal
-          pipe {
-            command => "''${pkgs.systemd}/bin/journalctl -f -o json"
-            type => "syslog" codec => json {}
-          }
+        example = literalExpression ''
+          '''
+            # Read from journal
+            pipe {
+              command => "''${pkgs.systemd}/bin/journalctl -f -o json"
+              type => "syslog" codec => json {}
+            }
+          '''
         '';
       };
 
@@ -151,6 +156,15 @@ in
         '';
       };
 
+      extraJvmOptions = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra JVM options, one per line (jvm.options format).";
+        example = ''
+          -Xms2g
+          -Xmx2g
+        '';
+      };
 
     };
   };
diff --git a/nixos/modules/services/logging/promtail.nix b/nixos/modules/services/logging/promtail.nix
index 34211687dc1db..95c83796ece69 100644
--- a/nixos/modules/services/logging/promtail.nix
+++ b/nixos/modules/services/logging/promtail.nix
@@ -7,6 +7,9 @@ let
   '';
 
   allowSystemdJournal = cfg.configuration ? scrape_configs && lib.any (v: v ? journal) cfg.configuration.scrape_configs;
+
+  allowPositionsFile = !lib.hasPrefix "/var/cache/promtail" positionsFile;
+  positionsFile = cfg.configuration.positions.filename;
 in {
   options.services.promtail = with types; {
     enable = mkEnableOption "the Promtail ingresser";
@@ -53,6 +56,7 @@ in {
         RestrictSUIDSGID = true;
         PrivateMounts = true;
         CacheDirectory = "promtail";
+        ReadWritePaths = lib.optional allowPositionsFile (builtins.dirOf positionsFile);
 
         User = "promtail";
         Group = "promtail";
diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix
index 35055311680bb..0a57bf20bd071 100644
--- a/nixos/modules/services/logging/syslog-ng.nix
+++ b/nixos/modules/services/logging/syslog-ng.nix
@@ -43,7 +43,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.syslogng;
-        defaultText = "pkgs.syslogng";
+        defaultText = literalExpression "pkgs.syslogng";
         description = ''
           The package providing syslog-ng binaries.
         '';
@@ -51,7 +51,7 @@ in {
       extraModulePaths = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [ "''${pkgs.syslogng_incubator}/lib/syslog-ng" ]
         '';
         description = ''
diff --git a/nixos/modules/services/mail/davmail.nix b/nixos/modules/services/mail/davmail.nix
index 374a3dd75c1c1..e9f31e6fb3901 100644
--- a/nixos/modules/services/mail/davmail.nix
+++ b/nixos/modules/services/mail/davmail.nix
@@ -42,7 +42,7 @@ in
           and <link xlink:href="http://davmail.sourceforge.net/advanced.html"/>
           for details on supported values.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             davmail.allowRemote = true;
             davmail.imapPort = 55555;
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index f3500f46e3556..c39827c5b867d 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -103,11 +103,12 @@ let
 
         plugin {
           quota_rule = *:storage=${cfg.quotaGlobalPerUser}
-          quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working
+          quota = count:User quota # per virtual mail user quota
           quota_status_success = DUNNO
           quota_status_nouser = DUNNO
           quota_status_overquota = "552 5.2.2 Mailbox is full"
           quota_grace = 10%%
+          quota_vsizes = yes
         }
       ''
     )
@@ -289,7 +290,7 @@ in
     modules = mkOption {
       type = types.listOf types.package;
       default = [];
-      example = literalExample "[ pkgs.dovecot_pigeonhole ]";
+      example = literalExpression "[ pkgs.dovecot_pigeonhole ]";
       description = ''
         Symlinks the contents of lib/dovecot of every given package into
         /etc/dovecot/modules. This will make the given modules available
@@ -339,7 +340,7 @@ in
         (list: listToAttrs (map (entry: { name = entry.name; value = removeAttrs entry ["name"]; }) list))
         (attrsOf (submodule mailboxes));
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           Spam = { specialUse = "Junk"; auto = "create"; };
         }
diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix
index 8927d84b478c6..7356db2b6a629 100644
--- a/nixos/modules/services/mail/exim.nix
+++ b/nixos/modules/services/mail/exim.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkIf mkOption singleton types;
+  inherit (lib) literalExpression mkIf mkOption singleton types;
   inherit (pkgs) coreutils;
   cfg = config.services.exim;
 in
@@ -60,7 +60,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.exim;
-        defaultText = "pkgs.exim";
+        defaultText = literalExpression "pkgs.exim";
         description = ''
           The Exim derivation to use.
           This can be used to enable features such as LDAP or PAM support.
@@ -104,7 +104,12 @@ in
       gid = config.ids.gids.exim;
     };
 
-    security.wrappers.exim.source = "${cfg.package}/bin/exim";
+    security.wrappers.exim =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${cfg.package}/bin/exim";
+      };
 
     systemd.services.exim = {
       description = "Exim Mail Daemon";
diff --git a/nixos/modules/services/mail/mail.nix b/nixos/modules/services/mail/mail.nix
index fed313e4738ef..fcc7ff6db91bc 100644
--- a/nixos/modules/services/mail/mail.nix
+++ b/nixos/modules/services/mail/mail.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, options, lib, ... }:
 
 with lib;
 
@@ -11,6 +11,7 @@ with lib;
     services.mail = {
 
       sendmailSetuidWrapper = mkOption {
+        type = types.nullOr options.security.wrappers.type.nestedTypes.elemType;
         default = null;
         internal = true;
         description = ''
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index 831175d5625f7..0c9b38b44b24d 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -87,8 +87,8 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.mailman;
-        defaultText = "pkgs.mailman";
-        example = literalExample "pkgs.mailman.override { archivers = []; }";
+        defaultText = literalExpression "pkgs.mailman";
+        example = literalExpression "pkgs.mailman.override { archivers = []; }";
         description = "Mailman package to use";
       };
 
diff --git a/nixos/modules/services/mail/offlineimap.nix b/nixos/modules/services/mail/offlineimap.nix
index 294e3806f94a2..4514775811900 100644
--- a/nixos/modules/services/mail/offlineimap.nix
+++ b/nixos/modules/services/mail/offlineimap.nix
@@ -25,14 +25,14 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.offlineimap;
-      defaultText = "pkgs.offlineimap";
+      defaultText = literalExpression "pkgs.offlineimap";
       description = "Offlineimap derivation to use.";
     };
 
     path = mkOption {
       type = types.listOf types.path;
       default = [];
-      example = literalExample "[ pkgs.pass pkgs.bash pkgs.notmuch ]";
+      example = literalExpression "[ pkgs.pass pkgs.bash pkgs.notmuch ]";
       description = "List of derivations to put in Offlineimap's path.";
     };
 
diff --git a/nixos/modules/services/mail/opensmtpd.nix b/nixos/modules/services/mail/opensmtpd.nix
index c838d3b949db9..e7632be280458 100644
--- a/nixos/modules/services/mail/opensmtpd.nix
+++ b/nixos/modules/services/mail/opensmtpd.nix
@@ -34,7 +34,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.opensmtpd;
-        defaultText = "pkgs.opensmtpd";
+        defaultText = literalExpression "pkgs.opensmtpd";
         description = "The OpenSMTPD package to use.";
       };
 
@@ -103,12 +103,15 @@ in {
     };
 
     security.wrappers.smtpctl = {
+      owner = "root";
       group = "smtpq";
+      setuid = false;
       setgid = true;
       source = "${cfg.package}/bin/smtpctl";
     };
 
-    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail security.wrappers.smtpctl;
+    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail
+      (security.wrappers.smtpctl // { program = "sendmail"; });
 
     systemd.tmpfiles.rules = [
       "d /var/spool/smtpd 711 root - - -"
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 9b0a5bba2feba..23d3574ae27c5 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -294,7 +294,7 @@ in
       };
 
       submissionOptions = mkOption {
-        type = types.attrs;
+        type = with types; attrsOf str;
         default = {
           smtpd_tls_security_level = "encrypt";
           smtpd_sasl_auth_enable = "yes";
@@ -312,7 +312,7 @@ in
       };
 
       submissionsOptions = mkOption {
-        type = types.attrs;
+        type = with types; attrsOf str;
         default = {
           smtpd_sasl_auth_enable = "yes";
           smtpd_client_restrictions = "permit_sasl_authenticated,reject";
@@ -505,6 +505,7 @@ in
       tlsTrustedAuthorities = mkOption {
         type = types.str;
         default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+        defaultText = literalExpression ''"''${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.
         '';
@@ -544,7 +545,7 @@ in
         type = types.lines;
         default = "";
         description = "
-          Entries for the virtual alias map, cf. man-page virtual(8).
+          Entries for the virtual alias map, cf. man-page virtual(5).
         ";
       };
 
@@ -673,6 +674,7 @@ in
       services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
         program = "sendmail";
         source = "${pkgs.postfix}/bin/sendmail";
+        owner = "root";
         group = setgidGroup;
         setuid = false;
         setgid = true;
@@ -681,6 +683,7 @@ in
       security.wrappers.mailq = {
         program = "mailq";
         source = "${pkgs.postfix}/bin/mailq";
+        owner = "root";
         group = setgidGroup;
         setuid = false;
         setgid = true;
@@ -689,6 +692,7 @@ in
       security.wrappers.postqueue = {
         program = "postqueue";
         source = "${pkgs.postfix}/bin/postqueue";
+        owner = "root";
         group = setgidGroup;
         setuid = false;
         setgid = true;
@@ -697,6 +701,7 @@ in
       security.wrappers.postdrop = {
         program = "postdrop";
         source = "${pkgs.postfix}/bin/postdrop";
+        owner = "root";
         group = setgidGroup;
         setuid = false;
         setgid = true;
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index f9b63000473c2..ac192c56aa604 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -7,7 +7,7 @@ let
   fpm = config.services.phpfpm.pools.roundcube;
   localDB = cfg.database.host == "localhost";
   user = cfg.database.username;
-  phpWithPspell = pkgs.php74.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
+  phpWithPspell = pkgs.php80.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
 in
 {
   options.services.roundcube = {
@@ -32,8 +32,9 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.roundcube;
+      defaultText = literalExpression "pkgs.roundcube";
 
-      example = literalExample ''
+      example = literalExpression ''
         roundcube.withPlugins (plugins: [ plugins.persistent_login ])
       '';
 
@@ -89,7 +90,7 @@ in
     dicts = mkOption {
       type = types.listOf types.package;
       default = [];
-      example = literalExample "with pkgs.aspellDicts; [ en fr de ]";
+      example = literalExpression "with pkgs.aspellDicts; [ en fr de ]";
       description = ''
         List of aspell dictionnaries for spell checking. If empty, spell checking is disabled.
       '';
diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index c78f464235aab..50208cbeb00a8 100644
--- a/nixos/modules/services/mail/rspamd.nix
+++ b/nixos/modules/services/mail/rspamd.nix
@@ -240,7 +240,7 @@ in
         description = ''
           Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
             "arc.conf".text = "allow_envfrom_empty = true;";
           }
@@ -253,7 +253,7 @@ in
         description = ''
           Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
             "arc.conf".text = "allow_envfrom_empty = true;";
           }
@@ -278,7 +278,7 @@ in
           normal = {};
           controller = {};
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             normal = {
               includes = [ "$CONFDIR/worker-normal.inc" ];
@@ -338,10 +338,6 @@ in
             smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
             non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
           };
-          example = {
-            smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
-            non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
-          };
         };
       };
     };
diff --git a/nixos/modules/services/mail/sympa.nix b/nixos/modules/services/mail/sympa.nix
index 491b6dba9aa42..f3578bef96ea4 100644
--- a/nixos/modules/services/mail/sympa.nix
+++ b/nixos/modules/services/mail/sympa.nix
@@ -153,7 +153,7 @@ in
         Email domains handled by this instance. There have
         to be MX records for keys of this attribute set.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         {
           "lists.example.org" = {
             webHost = "lists.example.org";
@@ -200,7 +200,7 @@ in
       name = mkOption {
         type = str;
         default = if cfg.database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa";
-        defaultText = ''if database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa"'';
+        defaultText = literalExpression ''if database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa"'';
         description = ''
           Database name. When using SQLite this must be an absolute
           path to the database file.
@@ -279,7 +279,7 @@ in
     settings = mkOption {
       type = attrsOf (oneOf [ str int bool ]);
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           default_home = "lists";
           viewlogs_page_size = 50;
@@ -314,7 +314,7 @@ in
         config.source = mkIf (config.text != null) (mkDefault (pkgs.writeText "sympa-${baseNameOf name}" config.text));
       }));
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "list_data/lists.example.org/help" = {
             text = "subject This list provides help to users";
diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix
new file mode 100644
index 0000000000000..278924b05cf28
--- /dev/null
+++ b/nixos/modules/services/matrix/mjolnir.nix
@@ -0,0 +1,242 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.mjolnir;
+
+  yamlConfig = {
+    inherit (cfg) dataPath managementRoom protectedRooms;
+
+    accessToken = "@ACCESS_TOKEN@"; # will be replaced in "generateConfig"
+    homeserverUrl =
+      if cfg.pantalaimon.enable then
+        "http://${cfg.pantalaimon.options.listenAddress}:${toString cfg.pantalaimon.options.listenPort}"
+      else
+        cfg.homeserverUrl;
+
+    rawHomeserverUrl = cfg.homeserverUrl;
+
+    pantalaimon = {
+      inherit (cfg.pantalaimon) username;
+
+      use = cfg.pantalaimon.enable;
+      password = "@PANTALAIMON_PASSWORD@"; # will be replaced in "generateConfig"
+    };
+  };
+
+  moduleConfigFile = pkgs.writeText "module-config.yaml" (
+    generators.toYAML { } (filterAttrs (_: v: v != null)
+      (fold recursiveUpdate { } [ yamlConfig cfg.settings ])));
+
+  # these config files will be merged one after the other to build the final config
+  configFiles = [
+    "${pkgs.mjolnir}/share/mjolnir/config/default.yaml"
+    moduleConfigFile
+  ];
+
+  # this will generate the default.yaml file with all configFiles as inputs and
+  # replace all secret strings using replace-secret
+  generateConfig = pkgs.writeShellScript "mjolnir-generate-config" (
+    let
+      yqEvalStr = concatImapStringsSep " * " (pos: _: "select(fileIndex == ${toString (pos - 1)})") configFiles;
+      yqEvalArgs = concatStringsSep " " configFiles;
+    in
+    ''
+      set -euo pipefail
+
+      umask 077
+
+      # mjolnir will try to load a config from "./config/default.yaml" in the working directory
+      # -> let's place the generated config there
+      mkdir -p ${cfg.dataPath}/config
+
+      # merge all config files into one, overriding settings of the previous one with the next config
+      # e.g. "eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' filea.yaml fileb.yaml" will merge filea.yaml with fileb.yaml
+      ${pkgs.yq-go}/bin/yq eval-all -P '${yqEvalStr}' ${yqEvalArgs} > ${cfg.dataPath}/config/default.yaml
+
+      ${optionalString (cfg.accessTokenFile != null) ''
+        ${pkgs.replace-secret}/bin/replace-secret '@ACCESS_TOKEN@' '${cfg.accessTokenFile}' ${cfg.dataPath}/config/default.yaml
+      ''}
+      ${optionalString (cfg.pantalaimon.passwordFile != null) ''
+        ${pkgs.replace-secret}/bin/replace-secret '@PANTALAIMON_PASSWORD@' '${cfg.pantalaimon.passwordFile}' ${cfg.dataPath}/config/default.yaml
+      ''}
+    ''
+  );
+in
+{
+  options.services.mjolnir = {
+    enable = mkEnableOption "Mjolnir, a moderation tool for Matrix";
+
+    homeserverUrl = mkOption {
+      type = types.str;
+      default = "https://matrix.org";
+      description = ''
+        Where the homeserver is located (client-server URL).
+
+        If <literal>pantalaimon.enable</literal> is <literal>true</literal>, this option will become the homeserver to which <literal>pantalaimon</literal> connects.
+        The listen address of <literal>pantalaimon</literal> will then become the <literal>homeserverUrl</literal> of <literal>mjolnir</literal>.
+      '';
+    };
+
+    accessTokenFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      description = ''
+        File containing the matrix access token for the <literal>mjolnir</literal> user.
+      '';
+    };
+
+    pantalaimon = mkOption {
+      description = ''
+        <literal>pantalaimon</literal> options (enables E2E Encryption support).
+
+        This will create a <literal>pantalaimon</literal> instance with the name "mjolnir".
+      '';
+      default = { };
+      type = types.submodule {
+        options = {
+          enable = mkEnableOption ''
+            If true, accessToken is ignored and the username/password below will be
+            used instead. The access token of the bot will be stored in the dataPath.
+          '';
+
+          username = mkOption {
+            type = types.str;
+            description = "The username to login with.";
+          };
+
+          passwordFile = mkOption {
+            type = with types; nullOr path;
+            default = null;
+            description = ''
+              File containing the matrix password for the <literal>mjolnir</literal> user.
+            '';
+          };
+
+          options = mkOption {
+            type = types.submodule (import ./pantalaimon-options.nix);
+            default = { };
+            description = ''
+              passthrough additional options to the <literal>pantalaimon</literal> service.
+            '';
+          };
+        };
+      };
+    };
+
+    dataPath = mkOption {
+      type = types.path;
+      default = "/var/lib/mjolnir";
+      description = ''
+        The directory the bot should store various bits of information in.
+      '';
+    };
+
+    managementRoom = mkOption {
+      type = types.str;
+      default = "#moderators:example.org";
+      description = ''
+        The room ID where people can use the bot. The bot has no access controls, so
+        anyone in this room can use the bot - secure your room!
+        This should be a room alias or room ID - not a matrix.to URL.
+        Note: <literal>mjolnir</literal> is fairly verbose - expect a lot of messages from it.
+      '';
+    };
+
+    protectedRooms = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = literalExpression ''
+        [
+          "https://matrix.to/#/#yourroom:example.org"
+          "https://matrix.to/#/#anotherroom:example.org"
+        ]
+      '';
+      description = ''
+        A list of rooms to protect (matrix.to URLs).
+      '';
+    };
+
+    settings = mkOption {
+      default = { };
+      type = (pkgs.formats.yaml { }).type;
+      example = literalExpression ''
+        {
+          autojoinOnlyIfManager = true;
+          automaticallyRedactForReasons = [ "spam" "advertising" ];
+        }
+      '';
+      description = ''
+        Additional settings (see <link xlink:href="https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml">mjolnir default config</link> for available settings). These settings will override settings made by the module config.
+      '';
+    };
+  };
+
+  config = mkIf config.services.mjolnir.enable {
+    assertions = [
+      {
+        assertion = !(cfg.pantalaimon.enable && cfg.pantalaimon.passwordFile == null);
+        message = "Specify pantalaimon.passwordFile";
+      }
+      {
+        assertion = !(cfg.pantalaimon.enable && cfg.accessTokenFile != null);
+        message = "Do not specify accessTokenFile when using pantalaimon";
+      }
+      {
+        assertion = !(!cfg.pantalaimon.enable && cfg.accessTokenFile == null);
+        message = "Specify accessTokenFile when not using pantalaimon";
+      }
+    ];
+
+    services.pantalaimon-headless.instances."mjolnir" = mkIf cfg.pantalaimon.enable
+      {
+        homeserver = cfg.homeserverUrl;
+      } // cfg.pantalaimon.options;
+
+    systemd.services.mjolnir = {
+      description = "mjolnir - a moderation tool for Matrix";
+      wants = [ "network-online.target" ] ++ optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
+      after = [ "network-online.target" ] ++ optionals (cfg.pantalaimon.enable) [ "pantalaimon-mjolnir.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = ''${pkgs.mjolnir}/bin/mjolnir'';
+        ExecStartPre = [ generateConfig ];
+        WorkingDirectory = cfg.dataPath;
+        StateDirectory = "mjolnir";
+        StateDirectoryMode = "0700";
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        User = "mjolnir";
+        Restart = "on-failure";
+
+        /* TODO: wait for #102397 to be resolved. Then load secrets from $CREDENTIALS_DIRECTORY+"/NAME"
+        DynamicUser = true;
+        LoadCredential = [] ++
+          optionals (cfg.accessTokenFile != null) [
+            "access_token:${cfg.accessTokenFile}"
+          ] ++
+          optionals (cfg.pantalaimon.passwordFile != null) [
+            "pantalaimon_password:${cfg.pantalaimon.passwordFile}"
+          ];
+        */
+      };
+    };
+
+    users = {
+      users.mjolnir = {
+        group = "mjolnir";
+        isSystemUser = true;
+      };
+      groups.mjolnir = { };
+    };
+  };
+
+  meta = {
+    doc = ./mjolnir.xml;
+    maintainers = with maintainers; [ jojosch ];
+  };
+}
diff --git a/nixos/modules/services/matrix/mjolnir.xml b/nixos/modules/services/matrix/mjolnir.xml
new file mode 100644
index 0000000000000..d462ddf7b01be
--- /dev/null
+++ b/nixos/modules/services/matrix/mjolnir.xml
@@ -0,0 +1,134 @@
+<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-mjolnir">
+ <title>Mjolnir (Matrix Moderation Tool)</title>
+ <para>
+  This chapter will show you how to set up your own, self-hosted
+  <link xlink:href="https://github.com/matrix-org/mjolnir">Mjolnir</link>
+  instance.
+ </para>
+ <para>
+  As an all-in-one moderation tool, it can protect your server from
+  malicious invites, spam messages, and whatever else you don't want.
+  In addition to server-level protection, Mjolnir is great for communities
+  wanting to protect their rooms without having to use their personal
+  accounts for moderation.
+ </para>
+ <para>
+  The bot by default includes support for bans, redactions, anti-spam,
+  server ACLs, room directory changes, room alias transfers, account
+  deactivation, room shutdown, and more.
+ </para>
+ <para>
+  See the <link xlink:href="https://github.com/matrix-org/mjolnir#readme">README</link>
+  page and the <link xlink:href="https://github.com/matrix-org/mjolnir/blob/main/docs/moderators.md">Moderator's guide</link>
+  for additional instructions on how to setup and use Mjolnir.
+ </para>
+ <para>
+  For <link linkend="opt-services.mjolnir.settings">additional settings</link>
+  see <link xlink:href="https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml">the default configuration</link>.
+ </para>
+ <section xml:id="module-services-mjolnir-setup">
+  <title>Mjolnir Setup</title>
+  <para>
+   First create a new Room which will be used as a management room for Mjolnir. In
+   this room, Mjolnir will log possible errors and debugging information. You'll
+   need to set this Room-ID in <link linkend="opt-services.mjolnir.managementRoom">services.mjolnir.managementRoom</link>.
+  </para>
+  <para>
+   Next, create a new user for Mjolnir on your homeserver, if not present already.
+  </para>
+  <para>
+   The Mjolnir Matrix user expects to be free of any rate limiting.
+   See <link xlink:href="https://github.com/matrix-org/synapse/issues/6286">Synapse #6286</link>
+   for an example on how to achieve this.
+  </para>
+  <para>
+   If you want Mjolnir to be able to deactivate users, move room aliases, shutdown rooms, etc.
+   you'll need to make the Mjolnir user a Matrix server admin.
+  </para>
+  <para>
+   Now invite the Mjolnir user to the management room.
+  </para>
+  <para>
+   It is recommended to use <link xlink:href="https://github.com/matrix-org/pantalaimon">Pantalaimon</link>,
+   so your management room can be encrypted. This also applies if you are looking to moderate an encrypted room.
+  </para>
+  <para>
+   To enable the Pantalaimon E2E Proxy for mjolnir, enable
+   <link linkend="opt-services.mjolnir.pantalaimon.enable">services.mjolnir.pantalaimon</link>. This will
+   autoconfigure a new Pantalaimon instance, which will connect to the homeserver
+   set in <link linkend="opt-services.mjolnir.homeserverUrl">services.mjolnir.homeserverUrl</link> and Mjolnir itself
+   will be configured to connect to the new Pantalaimon instance.
+  </para>
+<programlisting>
+{
+  services.mjolnir = {
+    enable = true;
+    <link linkend="opt-services.mjolnir.homeserverUrl">homeserverUrl</link> = "https://matrix.domain.tld";
+    <link linkend="opt-services.mjolnir.pantalaimon">pantalaimon</link> = {
+       <link linkend="opt-services.mjolnir.pantalaimon.enable">enable</link> = true;
+       <link linkend="opt-services.mjolnir.pantalaimon.username">username</link> = "mjolnir";
+       <link linkend="opt-services.mjolnir.pantalaimon.passwordFile">passwordFile</link> = "/run/secrets/mjolnir-password";
+    };
+    <link linkend="opt-services.mjolnir.protectedRooms">protectedRooms</link> = [
+      "https://matrix.to/#/!xxx:domain.tld"
+    ];
+    <link linkend="opt-services.mjolnir.managementRoom">managementRoom</link> = "!yyy:domain.tld";
+  };
+}
+</programlisting>
+ <section xml:id="module-services-mjolnir-setup-ems">
+  <title>Element Matrix Services (EMS)</title>
+  <para>
+   If you are using a managed <link xlink:href="https://ems.element.io/">"Element Matrix Services (EMS)"</link>
+   server, you will need to consent to the terms and conditions. Upon startup, an error
+   log entry with a URL to the consent page will be generated.
+  </para>
+ </section>
+ </section>
+
+ <section xml:id="module-services-mjolnir-matrix-synapse-antispam">
+  <title>Synapse Antispam Module</title>
+  <para>
+   A Synapse module is also available to apply the same rulesets the bot
+   uses across an entire homeserver.
+  </para>
+  <para>
+   To use the Antispam Module, add <package>matrix-synapse-plugins.matrix-synapse-mjolnir-antispam</package>
+   to the Synapse plugin list and enable the <literal>mjolnir.AntiSpam</literal> module.
+  </para>
+<programlisting>
+{
+  services.matrix-synapse = {
+    plugins = with pkgs; [
+      matrix-synapse-plugins.matrix-synapse-mjolnir-antispam
+    ];
+    extraConfig = ''
+      modules:
+        - module: mjolnir.AntiSpam
+          config:
+            # Prevent servers/users in the ban lists from inviting users on this
+            # server to rooms. Default true.
+            block_invites: true
+            # Flag messages sent by servers/users in the ban lists as spam. Currently
+            # this means that spammy messages will appear as empty to users. Default
+            # false.
+            block_messages: false
+            # Remove users from the user directory search by filtering matrix IDs and
+            # display names by the entries in the user ban list. Default false.
+            block_usernames: false
+            # The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
+            # this list cannot be room aliases or permalinks. This server is expected
+            # to already be joined to the room - Mjolnir will not automatically join
+            # these rooms.
+            ban_lists:
+              - "!roomid:example.org"
+    '';
+  };
+}
+</programlisting>
+ </section>
+</chapter>
diff --git a/nixos/modules/services/matrix/pantalaimon-options.nix b/nixos/modules/services/matrix/pantalaimon-options.nix
new file mode 100644
index 0000000000000..035c57540d09d
--- /dev/null
+++ b/nixos/modules/services/matrix/pantalaimon-options.nix
@@ -0,0 +1,70 @@
+{ config, lib, name, ... }:
+
+with lib;
+{
+  options = {
+    dataPath = mkOption {
+      type = types.path;
+      default = "/var/lib/pantalaimon-${name}";
+      description = ''
+        The directory where <literal>pantalaimon</literal> should store its state such as the database file.
+      '';
+    };
+
+    logLevel = mkOption {
+      type = types.enum [ "info" "warning" "error" "debug" ];
+      default = "warning";
+      description = ''
+        Set the log level of the daemon.
+      '';
+    };
+
+    homeserver = mkOption {
+      type = types.str;
+      example = "https://matrix.org";
+      description = ''
+        The URI of the homeserver that the <literal>pantalaimon</literal> proxy should
+        forward requests to, without the matrix API path but including
+        the http(s) schema.
+      '';
+    };
+
+    ssl = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether or not SSL verification should be enabled for outgoing
+        connections to the homeserver.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = ''
+        The address where the daemon will listen to client connections
+        for this homeserver.
+      '';
+    };
+
+    listenPort = mkOption {
+      type = types.port;
+      default = 8009;
+      description = ''
+        The port where the daemon will listen to client connections for
+        this homeserver. Note that the listen address/port combination
+        needs to be unique between different homeservers.
+      '';
+    };
+
+    extraSettings = mkOption {
+      type = types.attrs;
+      default = { };
+      description = ''
+        Extra configuration options. See
+        <link xlink:href="https://github.com/matrix-org/pantalaimon/blob/master/docs/man/pantalaimon.5.md">pantalaimon(5)</link>
+        for available options.
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/matrix/pantalaimon.nix b/nixos/modules/services/matrix/pantalaimon.nix
new file mode 100644
index 0000000000000..63b40099ca5dd
--- /dev/null
+++ b/nixos/modules/services/matrix/pantalaimon.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.pantalaimon-headless;
+
+  iniFmt = pkgs.formats.ini { };
+
+  mkConfigFile = name: instanceConfig: iniFmt.generate "pantalaimon.conf" {
+    Default = {
+      LogLevel = instanceConfig.logLevel;
+      Notifications = false;
+    };
+
+    ${name} = (recursiveUpdate
+      {
+        Homeserver = instanceConfig.homeserver;
+        ListenAddress = instanceConfig.listenAddress;
+        ListenPort = instanceConfig.listenPort;
+        SSL = instanceConfig.ssl;
+
+        # Set some settings to prevent user interaction for headless operation
+        IgnoreVerification = true;
+        UseKeyring = false;
+      }
+      instanceConfig.extraSettings
+    );
+  };
+
+  mkPantalaimonService = name: instanceConfig:
+    nameValuePair "pantalaimon-${name}" {
+      description = "pantalaimon instance ${name} - E2EE aware proxy daemon for matrix clients";
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = ''${pkgs.pantalaimon-headless}/bin/pantalaimon --config ${mkConfigFile name instanceConfig} --data-path ${instanceConfig.dataPath}'';
+        Restart = "on-failure";
+        DynamicUser = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        StateDirectory = "pantalaimon-${name}";
+      };
+    };
+in
+{
+  options.services.pantalaimon-headless.instances = mkOption {
+    default = { };
+    type = types.attrsOf (types.submodule (import ./pantalaimon-options.nix));
+    description = ''
+      Declarative instance config.
+
+      Note: to use pantalaimon interactively, e.g. for a Matrix client which does not
+      support End-to-end encryption (like <literal>fractal</literal>), refer to the home-manager module.
+    '';
+  };
+
+  config = mkIf (config.services.pantalaimon-headless.instances != { })
+    {
+      systemd.services = mapAttrs' mkPantalaimonService config.services.pantalaimon-headless.instances;
+    };
+
+  meta = {
+    maintainers = with maintainers; [ jojosch ];
+  };
+}
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 490f6c5a5c060..533a3d367a32a 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -74,7 +74,7 @@ in {
       transcoders = mkOption {
         type = types.listOf types.path;
         default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
-        defaultText= [ "\${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+        defaultText = literalExpression ''[ "''${pkgs.ffmpeg.bin}/bin/ffmpeg" ]'';
         description = ''
           List of paths to transcoder executables that should be accessible
           from Airsonic. Symlinks will be created to each executable inside
@@ -85,7 +85,7 @@ in {
       jre = mkOption {
         type = types.package;
         default = pkgs.jre8;
-        defaultText = literalExample "pkgs.jre8";
+        defaultText = literalExpression "pkgs.jre8";
         description = ''
           JRE package to use.
 
@@ -97,7 +97,7 @@ in {
       war = mkOption {
         type = types.path;
         default = "${pkgs.airsonic}/webapps/airsonic.war";
-        defaultText = "\${pkgs.airsonic}/webapps/airsonic.war";
+        defaultText = literalExpression ''"''${pkgs.airsonic}/webapps/airsonic.war"'';
         description = "Airsonic war file to use.";
       };
 
@@ -165,10 +165,12 @@ in {
 
     users.users.airsonic = {
       description = "Airsonic service user";
+      group = "airsonic";
       name = cfg.user;
       home = cfg.home;
       createHome = true;
       isSystemUser = true;
     };
+    users.groups.airsonic = {};
   };
 }
diff --git a/nixos/modules/services/misc/ananicy.nix b/nixos/modules/services/misc/ananicy.nix
new file mode 100644
index 0000000000000..f76f534fb4507
--- /dev/null
+++ b/nixos/modules/services/misc/ananicy.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ananicy;
+  configFile = pkgs.writeText "ananicy.conf" (generators.toKeyValue { } cfg.settings);
+  extraRules = pkgs.writeText "extraRules" cfg.extraRules;
+  servicename = if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
+in
+{
+  options = {
+    services.ananicy = {
+      enable = mkEnableOption "Ananicy, an auto nice daemon";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.ananicy;
+        defaultText = literalExpression "pkgs.ananicy";
+        example = literalExpression "pkgs.ananicy-cpp";
+        description = ''
+          Which ananicy package to use.
+        '';
+      };
+
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ int bool str ]);
+        default = { };
+        example = {
+          apply_nice = false;
+        };
+        description = ''
+          See <link xlink:href="https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf"/>
+        '';
+      };
+
+      extraRules = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Extra rules in json format on separate lines. See:
+          <link xlink:href="https://github.com/Nefelim4ag/Ananicy#configuration"/>
+          <link xlink:href="https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration"/>
+        '';
+        example = literalExpression ''
+          '''
+            { "name": "eog", "type": "Image-View" }
+            { "name": "fdupes", "type": "BG_CPUIO" }
+          '''
+        '';
+
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+      etc."ananicy.d".source = pkgs.runCommandLocal "ananicyfiles" { } ''
+        mkdir -p $out
+        # ananicy-cpp does not include rules or settings on purpose
+        cp -r ${pkgs.ananicy}/etc/ananicy.d/* $out
+        rm $out/ananicy.conf
+        cp ${configFile} $out/ananicy.conf
+        ${optionalString (cfg.extraRules != "") "cp ${extraRules} $out/nixRules.rules"}
+      '';
+    };
+
+    # ananicy and ananicy-cpp have different default settings
+    services.ananicy.settings =
+      let
+        mkOD = mkOptionDefault;
+      in
+      {
+        cgroup_load = mkOD true;
+        type_load = mkOD true;
+        rule_load = mkOD true;
+        apply_nice = mkOD true;
+        apply_ioclass = mkOD true;
+        apply_ionice = mkOD true;
+        apply_sched = mkOD true;
+        apply_oom_score_adj = mkOD true;
+        apply_cgroup = mkOD true;
+      } // (if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then {
+        # https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
+        loglevel = mkOD "warn"; # default is info but its spammy
+        cgroup_realtime_workaround = mkOD true;
+      } else {
+        # https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
+        check_disks_schedulers = mkOD true;
+        check_freq = mkOD 5;
+      });
+
+    systemd = {
+      # https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups applies to both ananicy and -cpp
+      enableUnifiedCgroupHierarchy = mkDefault false;
+      packages = [ cfg.package ];
+      services."${servicename}" = {
+        wantedBy = [ "default.target" ];
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [ artturin ];
+  };
+}
diff --git a/nixos/modules/services/misc/ankisyncd.nix b/nixos/modules/services/misc/ankisyncd.nix
index 5fc19649d3d95..69e471f4f577b 100644
--- a/nixos/modules/services/misc/ankisyncd.nix
+++ b/nixos/modules/services/misc/ankisyncd.nix
@@ -33,7 +33,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.ankisyncd;
-        defaultText = literalExample "pkgs.ankisyncd";
+        defaultText = literalExpression "pkgs.ankisyncd";
         description = "The package to use for the ankisyncd command.";
       };
 
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index 69dfadfe54e0d..d1856fff4aa40 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -102,14 +102,14 @@ in {
     package = mkOption {
       description = "The kafka package to use";
       default = pkgs.apacheKafka;
-      defaultText = "pkgs.apacheKafka";
+      defaultText = literalExpression "pkgs.apacheKafka";
       type = types.package;
     };
 
     jre = mkOption {
       description = "The JRE with which to run Kafka";
       default = cfg.package.passthru.jre;
-      defaultText = "pkgs.apacheKafka.passthru.jre";
+      defaultText = literalExpression "pkgs.apacheKafka.passthru.jre";
       type = types.package;
     };
 
@@ -120,10 +120,12 @@ in {
     environment.systemPackages = [cfg.package];
 
     users.users.apache-kafka = {
-      uid = config.ids.uids.apache-kafka;
+      isSystemUser = true;
+      group = "apache-kafka";
       description = "Apache Kafka daemon user";
       home = head cfg.logDirs;
     };
+    users.groups.apache-kafka = {};
 
     systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.logDirs;
 
diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix
index 541f0d2db19f4..5fce990afecec 100644
--- a/nixos/modules/services/misc/autofs.nix
+++ b/nixos/modules/services/misc/autofs.nix
@@ -29,7 +29,7 @@ in
 
       autoMaster = mkOption {
         type = types.str;
-        example = literalExample ''
+        example = literalExpression ''
           let
             mapConf = pkgs.writeText "auto" '''
              kernel    -ro,soft,intr       ftp.kernel.org:/pub/linux
diff --git a/nixos/modules/services/misc/bees.nix b/nixos/modules/services/misc/bees.nix
index 6b8cae84642f8..cb97a86b85921 100644
--- a/nixos/modules/services/misc/bees.nix
+++ b/nixos/modules/services/misc/bees.nix
@@ -61,7 +61,7 @@ let
       description = ''
         Extra command-line options passed to the daemon. See upstream bees documentation.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         [ "--thread-count" "4" ]
       '';
     };
@@ -75,7 +75,7 @@ in
       type = with types; attrsOf (submodule fsOptions);
       description = "BTRFS filesystems to run block-level deduplication on.";
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           root = {
             spec = "LABEL=root";
diff --git a/nixos/modules/services/misc/cfdyndns.nix b/nixos/modules/services/misc/cfdyndns.nix
index 15af1f50da1d6..5885617d7429e 100644
--- a/nixos/modules/services/misc/cfdyndns.nix
+++ b/nixos/modules/services/misc/cfdyndns.nix
@@ -48,7 +48,7 @@ in
       description = "CloudFlare Dynamic DNS Client";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      startAt = "5 minutes";
+      startAt = "*:0/5";
       serviceConfig = {
         Type = "simple";
         User = config.ids.uids.cfdyndns;
diff --git a/nixos/modules/services/misc/cgminer.nix b/nixos/modules/services/misc/cgminer.nix
index 662570f9451fe..60f75530723b7 100644
--- a/nixos/modules/services/misc/cgminer.nix
+++ b/nixos/modules/services/misc/cgminer.nix
@@ -35,7 +35,7 @@ in
 
       package = mkOption {
         default = pkgs.cgminer;
-        defaultText = "pkgs.cgminer";
+        defaultText = literalExpression "pkgs.cgminer";
         description = "Which cgminer derivation to use.";
         type = types.package;
       };
@@ -86,7 +86,7 @@ in
 
       config = mkOption {
         default = {};
-        type = (types.either types.bool types.int);
+        type = types.attrsOf (types.either types.bool types.int);
         description = "Additional config";
         example = {
           auto-fan = true;
@@ -110,10 +110,14 @@ in
 
     users.users = optionalAttrs (cfg.user == "cgminer") {
       cgminer = {
-        uid = config.ids.uids.cgminer;
+        isSystemUser = true;
+        group = "cgminer";
         description = "Cgminer user";
       };
     };
+    users.groups = optionalAttrs (cfg.user == "cgminer") {
+      cgminer = {};
+    };
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/misc/clipcat.nix b/nixos/modules/services/misc/clipcat.nix
index 128bb9a89d69c..8b749aa728969 100644
--- a/nixos/modules/services/misc/clipcat.nix
+++ b/nixos/modules/services/misc/clipcat.nix
@@ -12,7 +12,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.clipcat;
-      defaultText = "pkgs.clipcat";
+      defaultText = literalExpression "pkgs.clipcat";
       description = "clipcat derivation to use.";
     };
   };
diff --git a/nixos/modules/services/misc/clipmenu.nix b/nixos/modules/services/misc/clipmenu.nix
index 3ba050044cace..ef95985f8d8ae 100644
--- a/nixos/modules/services/misc/clipmenu.nix
+++ b/nixos/modules/services/misc/clipmenu.nix
@@ -12,7 +12,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.clipmenu;
-      defaultText = "pkgs.clipmenu";
+      defaultText = literalExpression "pkgs.clipmenu";
       description = "clipmenu derivation to use.";
     };
   };
diff --git a/nixos/modules/services/misc/confd.nix b/nixos/modules/services/misc/confd.nix
index c1ebdb3dde919..6c66786524ba8 100755
--- a/nixos/modules/services/misc/confd.nix
+++ b/nixos/modules/services/misc/confd.nix
@@ -64,7 +64,7 @@ in {
     package = mkOption {
       description = "Confd package to use.";
       default = pkgs.confd;
-      defaultText = "pkgs.confd";
+      defaultText = literalExpression "pkgs.confd";
       type = types.package;
     };
   };
diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 6e796a3a1fcec..96e2a4e7c2602 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -25,8 +25,8 @@ in
       DBs = mkOption {
         type = types.listOf types.package;
         default = with pkgs.dictdDBs; [ wiktionary wordnet ];
-        defaultText = "with pkgs.dictdDBs; [ wiktionary wordnet ]";
-        example = literalExample "[ pkgs.dictdDBs.nld2eng ]";
+        defaultText = literalExpression "with pkgs.dictdDBs; [ wiktionary wordnet ]";
+        example = literalExpression "[ pkgs.dictdDBs.nld2eng ]";
         description = "List of databases to make available.";
       };
 
diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix
index 24a259bb4d2bd..07c0613336aa2 100644
--- a/nixos/modules/services/misc/disnix.nix
+++ b/nixos/modules/services/misc/disnix.nix
@@ -31,7 +31,7 @@ in
         type = types.path;
         description = "The Disnix package";
         default = pkgs.disnix;
-        defaultText = "pkgs.disnix";
+        defaultText = literalExpression "pkgs.disnix";
       };
 
       enableProfilePath = mkEnableOption "exposing the Disnix profiles in the system's PATH";
@@ -39,7 +39,6 @@ in
       profiles = mkOption {
         type = types.listOf types.str;
         default = [ "default" ];
-        example = [ "default" ];
         description = "Names of the Disnix profiles to expose in the system's PATH";
       };
     };
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index e212f581c28a9..cb68a29c530bb 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -151,7 +151,9 @@ in {
         home = cfg.storagePath;
       }
       else {}) // {
+        group = "docker-registry";
         isSystemUser = true;
       };
+    users.groups.docker-registry = {};
   };
 }
diff --git a/nixos/modules/services/misc/dwm-status.nix b/nixos/modules/services/misc/dwm-status.nix
index b98a42e6a6d2d..5f591b3c5d41a 100644
--- a/nixos/modules/services/misc/dwm-status.nix
+++ b/nixos/modules/services/misc/dwm-status.nix
@@ -27,8 +27,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.dwm-status;
-        defaultText = "pkgs.dwm-status";
-        example = "pkgs.dwm-status.override { enableAlsaUtils = false; }";
+        defaultText = literalExpression "pkgs.dwm-status";
+        example = literalExpression "pkgs.dwm-status.override { enableAlsaUtils = false; }";
         description = ''
           Which dwm-status package to use.
         '';
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index eb266f043ebcf..c4ea091a03802 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -123,7 +123,7 @@ in {
       '';
       type = types.attrsOf types.str;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "CORS" = "*";
           "NAME" = "default-name";
@@ -187,9 +187,11 @@ in {
     environment.systemPackages = [ pkgs.etcd ];
 
     users.users.etcd = {
-      uid = config.ids.uids.etcd;
+      isSystemUser = true;
+      group = "etcd";
       description = "Etcd daemon user";
       home = cfg.dataDir;
     };
+    users.groups.etcd = {};
   };
 }
diff --git a/nixos/modules/services/misc/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
index b6bd6e9fd37bc..dd84ac37b0d5e 100644
--- a/nixos/modules/services/misc/etebase-server.nix
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -97,13 +97,13 @@ in
               static_root = mkOption {
                 type = types.str;
                 default = "${cfg.dataDir}/static";
-                defaultText = "\${config.services.etebase-server.dataDir}/static";
+                defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/static"'';
                 description = "The directory for static files.";
               };
               media_root = mkOption {
                 type = types.str;
                 default = "${cfg.dataDir}/media";
-                defaultText = "\${config.services.etebase-server.dataDir}/media";
+                defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/media"'';
                 description = "The media directory.";
               };
             };
@@ -126,7 +126,7 @@ in
               name = mkOption {
                 type = types.str;
                 default = "${cfg.dataDir}/db.sqlite3";
-                defaultText = "\${config.services.etebase-server.dataDir}/db.sqlite3";
+                defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/db.sqlite3"'';
                 description = "The database name.";
               };
             };
diff --git a/nixos/modules/services/misc/felix.nix b/nixos/modules/services/misc/felix.nix
index 8d438bb9eb197..0283de128afe4 100644
--- a/nixos/modules/services/misc/felix.nix
+++ b/nixos/modules/services/misc/felix.nix
@@ -22,7 +22,7 @@ in
       bundles = mkOption {
         type = types.listOf types.package;
         default = [ pkgs.felix_remoteshell ];
-        defaultText = "[ pkgs.felix_remoteshell ]";
+        defaultText = literalExpression "[ pkgs.felix_remoteshell ]";
         description = "List of bundles that should be activated on startup";
       };
 
diff --git a/nixos/modules/services/misc/freeswitch.nix b/nixos/modules/services/misc/freeswitch.nix
index b42f36e86637d..472b0b73ff69d 100644
--- a/nixos/modules/services/misc/freeswitch.nix
+++ b/nixos/modules/services/misc/freeswitch.nix
@@ -32,8 +32,8 @@ in {
       configTemplate = mkOption {
         type = types.path;
         default = "${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
-        defaultText = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
-        example = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/minimal";
+        defaultText = literalExpression ''"''${config.services.freeswitch.package}/share/freeswitch/conf/vanilla"'';
+        example = literalExpression ''"''${config.services.freeswitch.package}/share/freeswitch/conf/minimal"'';
         description = ''
           Configuration template to use.
           See available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
@@ -43,7 +43,7 @@ in {
       configDir = mkOption {
         type = with types; attrsOf path;
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           {
             "freeswitch.xml" = ./freeswitch.xml;
             "dialplan/default.xml" = pkgs.writeText "dialplan-default.xml" '''
@@ -61,8 +61,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.freeswitch;
-        defaultText = literalExample "pkgs.freeswitch";
-        example = literalExample "pkgs.freeswitch";
+        defaultText = literalExpression "pkgs.freeswitch";
         description = ''
           FreeSWITCH package.
         '';
diff --git a/nixos/modules/services/misc/gammu-smsd.nix b/nixos/modules/services/misc/gammu-smsd.nix
index 552725f1384d0..d4bb58d81dde3 100644
--- a/nixos/modules/services/misc/gammu-smsd.nix
+++ b/nixos/modules/services/misc/gammu-smsd.nix
@@ -202,8 +202,8 @@ in {
   config = mkIf cfg.enable {
     users.users.${cfg.user} = {
       description = "gammu-smsd user";
-      uid = config.ids.uids.gammu-smsd;
-      extraGroups = [ "${cfg.device.group}" ];
+      isSystemUser = true;
+      group = cfg.device.group;
     };
 
     environment.systemPackages = with cfg.backend; [ gammuPackage ]
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 8322b7c090227..c0f7661c5698b 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -32,7 +32,7 @@ in
       package = mkOption {
         default = pkgs.gitea;
         type = types.package;
-        defaultText = "pkgs.gitea";
+        defaultText = literalExpression "pkgs.gitea";
         description = "gitea derivation to use";
       };
 
@@ -55,7 +55,7 @@ in
           description = "Root path for log files.";
         };
         level = mkOption {
-          default = "Trace";
+          default = "Info";
           type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
           description = "General log level.";
         };
@@ -122,7 +122,7 @@ in
         socket = mkOption {
           type = types.nullOr types.path;
           default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
-          defaultText = "null";
+          defaultText = literalExpression "null";
           example = "/run/mysqld/mysqld.sock";
           description = "Path to the unix socket file to use for authentication.";
         };
@@ -255,8 +255,9 @@ in
       };
 
       staticRootPath = mkOption {
-        type = types.str;
-        default = "${gitea.data}";
+        type = types.either types.str types.path;
+        default = gitea.data;
+        defaultText = literalExpression "package.data";
         example = "/var/lib/gitea/data";
         description = "Upper level of template and static files path.";
       };
@@ -287,7 +288,7 @@ in
           Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
           for details on supported values.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             "cron.sync_external_users" = {
               RUN_AT_START = true;
@@ -348,7 +349,7 @@ in
       server = mkMerge [
         {
           DOMAIN = cfg.domain;
-          STATIC_ROOT_PATH = cfg.staticRootPath;
+          STATIC_ROOT_PATH = toString cfg.staticRootPath;
           LFS_JWT_SECRET = "#lfsjwtsecret#";
           ROOT_URL = cfg.rootUrl;
         }
diff --git a/nixos/modules/services/misc/gitit.nix b/nixos/modules/services/misc/gitit.nix
index f09565283f3c1..ceb186c0f0492 100644
--- a/nixos/modules/services/misc/gitit.nix
+++ b/nixos/modules/services/misc/gitit.nix
@@ -36,15 +36,15 @@ let
 
       haskellPackages = mkOption {
         default = pkgs.haskellPackages;
-        defaultText = "pkgs.haskellPackages";
-        example = literalExample "pkgs.haskell.packages.ghc784";
+        defaultText = literalExpression "pkgs.haskellPackages";
+        example = literalExpression "pkgs.haskell.packages.ghc784";
         description = "haskellPackages used to build gitit and plugins.";
       };
 
       extraPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
         default = self: [];
-        example = literalExample ''
+        example = literalExpression ''
           haskellPackages: [
             haskellPackages.wreq
           ]
@@ -665,9 +665,9 @@ in
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ curl ]
              ++ optional cfg.pdfExport texlive.combined.scheme-basic
-	     ++ optional (cfg.repositoryType == "darcs") darcs
-	     ++ optional (cfg.repositoryType == "mercurial") mercurial
-	     ++ optional (cfg.repositoryType == "git") git;
+             ++ optional (cfg.repositoryType == "darcs") darcs
+             ++ optional (cfg.repositoryType == "mercurial") mercurial
+             ++ optional (cfg.repositoryType == "git") git;
 
       preStart = let
         gm = "gitit@${config.networking.hostName}";
@@ -684,35 +684,35 @@ in
           fi
         done
         cd ${repositoryPath}
-	${
-	  if repositoryType == "darcs" then
-	  ''
-	  if [ ! -d _darcs ]
-	  then
-	    ${pkgs.darcs}/bin/darcs initialize
-	    echo "${gm}" > _darcs/prefs/email
-	  ''
-	  else if repositoryType == "mercurial" then
-	  ''
-	  if [ ! -d .hg ]
-	  then
-	    ${pkgs.mercurial}/bin/hg init
-	    cat >> .hg/hgrc <<NAMED
+        ${
+          if repositoryType == "darcs" then
+          ''
+          if [ ! -d _darcs ]
+          then
+            ${pkgs.darcs}/bin/darcs initialize
+            echo "${gm}" > _darcs/prefs/email
+          ''
+          else if repositoryType == "mercurial" then
+          ''
+          if [ ! -d .hg ]
+          then
+            ${pkgs.mercurial}/bin/hg init
+            cat >> .hg/hgrc <<NAMED
 [ui]
 username = gitit ${gm}
 NAMED
-	  ''
-	  else
-	  ''
-	  if [ ! -d  .git ]
+          ''
+          else
+          ''
+          if [ ! -d  .git ]
           then
             ${pkgs.git}/bin/git init
             ${pkgs.git}/bin/git config user.email "${gm}"
             ${pkgs.git}/bin/git config user.name "gitit"
-	  ''}
+          ''}
           chown ${uid}:${gid} -R ${repositoryPath}
           fi
-	cd -
+        cd -
       '';
 
       serviceConfig = {
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 805deeee0c04e..b2abe70627d0d 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -239,36 +239,36 @@ in {
       packages.gitlab = mkOption {
         type = types.package;
         default = pkgs.gitlab;
-        defaultText = "pkgs.gitlab";
+        defaultText = literalExpression "pkgs.gitlab";
         description = "Reference to the gitlab package";
-        example = "pkgs.gitlab-ee";
+        example = literalExpression "pkgs.gitlab-ee";
       };
 
       packages.gitlab-shell = mkOption {
         type = types.package;
         default = pkgs.gitlab-shell;
-        defaultText = "pkgs.gitlab-shell";
+        defaultText = literalExpression "pkgs.gitlab-shell";
         description = "Reference to the gitlab-shell package";
       };
 
       packages.gitlab-workhorse = mkOption {
         type = types.package;
         default = pkgs.gitlab-workhorse;
-        defaultText = "pkgs.gitlab-workhorse";
+        defaultText = literalExpression "pkgs.gitlab-workhorse";
         description = "Reference to the gitlab-workhorse package";
       };
 
       packages.gitaly = mkOption {
         type = types.package;
         default = pkgs.gitaly;
-        defaultText = "pkgs.gitaly";
+        defaultText = literalExpression "pkgs.gitaly";
         description = "Reference to the gitaly package";
       };
 
       packages.pages = mkOption {
         type = types.package;
         default = pkgs.gitlab-pages;
-        defaultText = "pkgs.gitlab-pages";
+        defaultText = literalExpression "pkgs.gitlab-pages";
         description = "Reference to the gitlab-pages package";
       };
 
@@ -356,7 +356,7 @@ in {
       backup.uploadOptions = mkOption {
         type = types.attrs;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             # Fog storage connection settings, see http://fog.io/storage/
             connection = {
@@ -543,12 +543,10 @@ in {
         };
         certFile = mkOption {
           type = types.path;
-          default = null;
           description = "Path to GitLab container registry certificate.";
         };
         keyFile = mkOption {
           type = types.path;
-          default = null;
           description = "Path to GitLab container registry certificate-key.";
         };
         defaultForProjects = mkOption {
@@ -821,10 +819,44 @@ in {
         '';
       };
 
+      logrotate = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Enable rotation of log files.
+          '';
+        };
+
+        frequency = mkOption {
+          type = types.str;
+          default = "daily";
+          description = "How often to rotate the logs.";
+        };
+
+        keep = mkOption {
+          type = types.int;
+          default = 30;
+          description = "How many rotations to keep.";
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = ''
+            copytruncate
+            compress
+          '';
+          description = ''
+            Extra logrotate config options for this path. Refer to
+            <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+          '';
+        };
+      };
+
       extraConfig = mkOption {
         type = types.attrs;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             gitlab = {
               default_projects_features = {
@@ -932,6 +964,21 @@ in {
       ensureUsers = singleton { name = cfg.databaseUsername; };
     };
 
+    # Enable rotation of log files
+    services.logrotate = {
+      enable = cfg.logrotate.enable;
+      paths = {
+        gitlab = {
+          path = "${cfg.statePath}/log/*.log";
+          user = cfg.user;
+          group = cfg.group;
+          frequency = cfg.logrotate.frequency;
+          keep = cfg.logrotate.keep;
+          extraConfig = cfg.logrotate.extraConfig;
+        };
+      };
+    };
+
     # The postgresql module doesn't currently support concepts like
     # objects owners and extensions; for now we tack on what's needed
     # here.
diff --git a/nixos/modules/services/misc/gitolite.nix b/nixos/modules/services/misc/gitolite.nix
index 190ea9212d2aa..810ef1f21b9c9 100644
--- a/nixos/modules/services/misc/gitolite.nix
+++ b/nixos/modules/services/misc/gitolite.nix
@@ -64,11 +64,13 @@ in
       extraGitoliteRc = mkOption {
         type = types.lines;
         default = "";
-        example = literalExample ''
-          $RC{UMASK} = 0027;
-          $RC{SITE_INFO} = 'This is our private repository host';
-          push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature
-          @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
+        example = literalExpression ''
+          '''
+            $RC{UMASK} = 0027;
+            $RC{SITE_INFO} = 'This is our private repository host';
+            push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature
+            @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
+          '''
         '';
         description = ''
           Extra configuration to append to the default <literal>~/.gitolite.rc</literal>.
diff --git a/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix
index fafea10daba77..6494578f76472 100644
--- a/nixos/modules/services/misc/gpsd.nix
+++ b/nixos/modules/services/misc/gpsd.nix
@@ -88,6 +88,7 @@ in
 
     users.users.gpsd =
       { inherit uid;
+        group = "gpsd";
         description = "gpsd daemon user";
         home = "/var/empty";
       };
diff --git a/nixos/modules/services/misc/greenclip.nix b/nixos/modules/services/misc/greenclip.nix
index 9152a782d7f04..32e8d746cb5c6 100644
--- a/nixos/modules/services/misc/greenclip.nix
+++ b/nixos/modules/services/misc/greenclip.nix
@@ -12,7 +12,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.haskellPackages.greenclip;
-      defaultText = "pkgs.haskellPackages.greenclip";
+      defaultText = literalExpression "pkgs.haskellPackages.greenclip";
       description = "greenclip derivation to use.";
     };
   };
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 73ec3b9a17a2c..8279d075bafbc 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -112,7 +112,7 @@ in {
             emptyValue.value = {};
           };
         in valueType;
-      example = literalExample ''
+      example = literalExpression ''
         {
           homeassistant = {
             name = "Home";
@@ -152,7 +152,7 @@ in {
       default = null;
       type = with types; nullOr attrs;
       # from https://www.home-assistant.io/lovelace/yaml-mode/
-      example = literalExample ''
+      example = literalExpression ''
         {
           title = "My Awesome Home";
           views = [ {
@@ -188,13 +188,13 @@ in {
       default = pkgs.home-assistant.overrideAttrs (oldAttrs: {
         doInstallCheck = false;
       });
-      defaultText = literalExample ''
+      defaultText = literalExpression ''
         pkgs.home-assistant.overrideAttrs (oldAttrs: {
           doInstallCheck = false;
         })
       '';
       type = types.package;
-      example = literalExample ''
+      example = literalExpression ''
         pkgs.home-assistant.override {
           extraPackages = ps: with ps; [ colorlog ];
         }
@@ -310,11 +310,13 @@ in {
           "serial_pm"
           "sms"
           "upb"
+          "usb"
           "velbus"
           "w800rf32"
           "xbee"
           "zha"
           "zwave"
+          "zwave_js"
         ];
       in {
         ExecStart = "${package}/bin/hass --runner --config '${cfg.configDir}'";
diff --git a/nixos/modules/services/misc/ihaskell.nix b/nixos/modules/services/misc/ihaskell.nix
index c7332b87803a9..9978e8a46534e 100644
--- a/nixos/modules/services/misc/ihaskell.nix
+++ b/nixos/modules/services/misc/ihaskell.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.ihaskell;
   ihaskell = pkgs.ihaskell.override {
-    packages = self: cfg.extraPackages self;
+    packages = cfg.extraPackages;
   };
 
 in
@@ -22,8 +22,9 @@ in
 
       extraPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
-        default = self: [];
-        example = literalExample ''
+        default = haskellPackages: [];
+        defaultText = literalExpression "haskellPackages: []";
+        example = literalExpression ''
           haskellPackages: [
             haskellPackages.wreq
             haskellPackages.lens
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index f2dc6635df933..c2144d4a9a9f5 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -38,7 +38,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.jackett;
-        defaultText = "pkgs.jackett";
+        defaultText = literalExpression "pkgs.jackett";
         description = "Jackett package to use.";
       };
     };
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
index 6d64acc029101..b9d54f27edc21 100644
--- a/nixos/modules/services/misc/jellyfin.nix
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -19,7 +19,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.jellyfin;
-        example = literalExample "pkgs.jellyfin";
+        defaultText = literalExpression "pkgs.jellyfin";
         description = ''
           Jellyfin package to use.
         '';
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index e6b9dd234a9bd..7b3780b5cc9ff 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -19,6 +19,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.klipper;
+        defaultText = literalExpression "pkgs.klipper";
         description = "The Klipper package.";
       };
 
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index 8ff1adadcf239..20153c7e61a61 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -19,7 +19,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.lidarr;
-        defaultText = "pkgs.lidarr";
+        defaultText = literalExpression "pkgs.lidarr";
         description = "The Lidarr package to use";
       };
 
diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix
index 4b9a04be7c297..dd6c5ef9aa005 100644
--- a/nixos/modules/services/misc/mame.nix
+++ b/nixos/modules/services/misc/mame.nix
@@ -45,8 +45,10 @@ in
     environment.systemPackages = [ pkgs.mame ];
 
     security.wrappers."${mame}" = {
-      source = "${pkgs.mame}/bin/${mame}";
+      owner = "root";
+      group = "root";
       capabilities = "cap_net_admin,cap_net_raw+eip";
+      source = "${pkgs.mame}/bin/${mame}";
     };
 
     systemd.services.mame = {
diff --git a/nixos/modules/services/misc/matrix-appservice-discord.nix b/nixos/modules/services/misc/matrix-appservice-discord.nix
index 71d1227f4ff72..c448614eca328 100644
--- a/nixos/modules/services/misc/matrix-appservice-discord.nix
+++ b/nixos/modules/services/misc/matrix-appservice-discord.nix
@@ -31,7 +31,7 @@ in {
             botToken = "";
           };
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             bridge = {
               domain = "public-domain.tld";
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index e150a1aaaad13..950c72c6e589b 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -122,10 +122,18 @@ in {
   options = {
     services.matrix-synapse = {
       enable = mkEnableOption "matrix.org synapse";
+      configFile = mkOption {
+        type = types.str;
+        readOnly = true;
+        description = ''
+          Path to the configuration file on the target system. Useful to configure e.g. workers
+          that also need this.
+        '';
+      };
       package = mkOption {
         type = types.package;
         default = pkgs.matrix-synapse;
-        defaultText = "pkgs.matrix-synapse";
+        defaultText = literalExpression "pkgs.matrix-synapse";
         description = ''
           Overridable attribute of the matrix synapse server package to use.
         '';
@@ -133,7 +141,7 @@ in {
       plugins = mkOption {
         type = types.listOf types.package;
         default = [ ];
-        example = literalExample ''
+        example = literalExpression ''
           with config.services.matrix-synapse.package.plugins; [
             matrix-synapse-ldap3
             matrix-synapse-pam
@@ -706,6 +714,8 @@ in {
       }
     ];
 
+    services.matrix-synapse.configFile = "${configFile}";
+
     users.users.matrix-synapse = {
       group = "matrix-synapse";
       home = cfg.dataDir;
diff --git a/nixos/modules/services/misc/mautrix-facebook.nix b/nixos/modules/services/misc/mautrix-facebook.nix
new file mode 100644
index 0000000000000..e046c791ac013
--- /dev/null
+++ b/nixos/modules/services/misc/mautrix-facebook.nix
@@ -0,0 +1,195 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mautrix-facebook;
+  settingsFormat = pkgs.formats.json {};
+  settingsFile = settingsFormat.generate "mautrix-facebook-config.json" cfg.settings;
+
+  puppetRegex = concatStringsSep
+    ".*"
+    (map
+      escapeRegex
+      (splitString
+        "{userid}"
+        cfg.settings.bridge.username_template));
+in {
+  options = {
+    services.mautrix-facebook = {
+      enable = mkEnableOption "Mautrix-Facebook, a Matrix-Facebook hybrid puppeting/relaybot bridge";
+
+      settings = mkOption rec {
+        apply = recursiveUpdate default;
+        type = settingsFormat.type;
+        default = {
+          homeserver = {
+            address = "http://localhost:8008";
+          };
+
+          appservice = rec {
+            address = "http://${hostname}:${toString port}";
+            hostname = "localhost";
+            port = 29319;
+
+            database = "postgresql://";
+
+            bot_username = "facebookbot";
+          };
+
+          metrics.enabled = false;
+          manhole.enabled = false;
+
+          bridge = {
+            encryption = {
+              allow = true;
+              default = true;
+            };
+            username_template = "facebook_{userid}";
+          };
+
+          logging = {
+            version = 1;
+            formatters.journal_fmt.format = "%(name)s: %(message)s";
+            handlers.journal = {
+              class = "systemd.journal.JournalHandler";
+              formatter = "journal_fmt";
+              SYSLOG_IDENTIFIER = "mautrix-facebook";
+            };
+            root = {
+              level = "INFO";
+              handlers = ["journal"];
+            };
+          };
+        };
+        example = literalExpression ''
+          {
+            homeserver = {
+              address = "http://localhost:8008";
+              domain = "mydomain.example";
+            };
+
+            bridge.permissions = {
+              "@admin:mydomain.example" = "admin";
+              "mydomain.example" = "user";
+            };
+          }
+        '';
+        description = ''
+          <filename>config.yaml</filename> configuration as a Nix attribute set.
+          Configuration options should match those described in
+          <link xlink:href="https://github.com/mautrix/facebook/blob/master/mautrix_facebook/example-config.yaml">
+          example-config.yaml</link>.
+          </para>
+
+          <para>
+          Secret tokens should be specified using <option>environmentFile</option>
+          instead of this world-readable attribute set.
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          File containing environment variables to be passed to the mautrix-telegram service.
+
+          Any config variable can be overridden by setting <literal>MAUTRIX_FACEBOOK_SOME_KEY</literal> to override the <literal>some.key</literal> variable.
+        '';
+      };
+
+      configurePostgresql = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable PostgreSQL and create a user and database for mautrix-facebook. The default <literal>settings</literal> reference this database, if you disable this option you must provide a database URL.
+        '';
+      };
+
+      registrationData = mkOption {
+        type = types.attrs;
+        default = {};
+        description = ''
+          Output data for appservice registration. Simply make any desired changes and serialize to JSON. Note that this data contains secrets so think twice before putting it into the nix store.
+
+          Currently <literal>as_token</literal> and <literal>hs_token</literal> need to be added as they are not known to this module.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.mautrix-facebook = {
+      group = "mautrix-facebook";
+      isSystemUser = true;
+    };
+
+    services.postgresql = mkIf cfg.configurePostgresql {
+      ensureDatabases = ["mautrix-facebook"];
+      ensureUsers = [{
+        name = "mautrix-facebook";
+        ensurePermissions = {
+          "DATABASE \"mautrix-facebook\"" = "ALL PRIVILEGES";
+        };
+      }];
+    };
+
+    systemd.services.mautrix-facebook = rec {
+      wantedBy = [ "multi-user.target" ];
+      wants = [
+        "network-online.target"
+      ] ++ optional config.services.matrix-synapse.enable "matrix-synapse.service"
+        ++ optional cfg.configurePostgresql "postgresql.service";
+      after = wants;
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+
+        User = "mautrix-facebook";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        PrivateTmp = true;
+
+        EnvironmentFile = cfg.environmentFile;
+
+        ExecStart = ''
+          ${pkgs.mautrix-facebook}/bin/mautrix-facebook --config=${settingsFile}
+        '';
+      };
+    };
+
+    services.mautrix-facebook = {
+      registrationData = {
+        id = "mautrix-facebook";
+
+        namespaces = {
+          users = [
+            {
+              exclusive = true;
+              regex = escapeRegex "@${cfg.settings.appservice.bot_username}:${cfg.settings.homeserver.domain}";
+            }
+            {
+              exclusive = true;
+              regex = "@${puppetRegex}:${escapeRegex cfg.settings.homeserver.domain}";
+            }
+          ];
+          aliases = [];
+        };
+
+        url = cfg.settings.appservice.address;
+        sender_localpart = "mautrix-facebook-sender";
+
+        rate_limited = false;
+        "de.sorunome.msc2409.push_ephemeral" = true;
+        push_ephemeral = true;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ kevincox ];
+}
diff --git a/nixos/modules/services/misc/mautrix-telegram.nix b/nixos/modules/services/misc/mautrix-telegram.nix
index 717cf7936ead0..59d0b6824090c 100644
--- a/nixos/modules/services/misc/mautrix-telegram.nix
+++ b/nixos/modules/services/misc/mautrix-telegram.nix
@@ -60,7 +60,7 @@ in {
             };
           };
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             homeserver = {
               address = "http://localhost:8008";
diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix
index e22d1ed61f992..d80b6fafc2cf6 100644
--- a/nixos/modules/services/misc/mbpfan.nix
+++ b/nixos/modules/services/misc/mbpfan.nix
@@ -13,7 +13,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.mbpfan;
-      defaultText = "pkgs.mbpfan";
+      defaultText = literalExpression "pkgs.mbpfan";
       description = ''
         The package used for the mbpfan daemon.
       '';
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index a19b73889ce45..383090575b22a 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -216,10 +216,11 @@ in {
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.mediatomb";
+        example = literalExpression "pkgs.mediatomb";
         default = pkgs.gerbera;
+        defaultText = literalExpression "pkgs.gerbera";
         description = ''
-          Underlying package to be used with the module (default: pkgs.gerbera).
+          Underlying package to be used with the module.
         '';
       };
 
@@ -325,7 +326,7 @@ in {
 
       mediaDirectories = mkOption {
         type = with types; listOf (submodule mediaDirectory);
-        default = {};
+        default = [];
         description = ''
           Declare media directories to index.
         '';
diff --git a/nixos/modules/services/misc/mx-puppet-discord.nix b/nixos/modules/services/misc/mx-puppet-discord.nix
index 11116f7c34890..c34803f97223f 100644
--- a/nixos/modules/services/misc/mx-puppet-discord.nix
+++ b/nixos/modules/services/misc/mx-puppet-discord.nix
@@ -45,7 +45,7 @@ in {
             lineDateFormat = "MMM-D HH:mm:ss.SSS";
           };
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             bridge = {
               bindAddress = "localhost";
diff --git a/nixos/modules/services/misc/n8n.nix b/nixos/modules/services/misc/n8n.nix
index 516d0f70ef0b8..27616e5f8226e 100644
--- a/nixos/modules/services/misc/n8n.nix
+++ b/nixos/modules/services/misc/n8n.nix
@@ -66,7 +66,7 @@ in
         RestrictNamespaces = "yes";
         RestrictRealtime = "yes";
         RestrictSUIDSGID = "yes";
-        MemoryDenyWriteExecute = "yes";
+        MemoryDenyWriteExecute = "no"; # v8 JIT requires memory segments to be Writable-Executable.
         LockPersonality = "yes";
       };
     };
diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix
index 301af76c336af..0c562343d85d3 100644
--- a/nixos/modules/services/misc/nitter.nix
+++ b/nixos/modules/services/misc/nitter.nix
@@ -79,7 +79,7 @@ in
         staticDir = mkOption {
           type = types.path;
           default = "${pkgs.nitter}/share/nitter/public";
-          defaultText = "\${pkgs.nitter}/share/nitter/public";
+          defaultText = literalExpression ''"''${pkgs.nitter}/share/nitter/public"'';
           description = "Path to the static files directory.";
         };
 
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 70b27b7d3d096..4ea45888e5fc5 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -82,10 +82,19 @@ in
 
     nix = {
 
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable Nix.
+          Disabling Nix makes the system hard to modify and the Nix programs and configuration will not be made available by NixOS itself.
+        '';
+      };
+
       package = mkOption {
         type = types.package;
         default = pkgs.nix;
-        defaultText = "pkgs.nix";
+        defaultText = literalExpression "pkgs.nix";
         description = ''
           This option specifies the Nix package instance to use throughout the system.
         '';
@@ -175,22 +184,51 @@ in
         '';
       };
 
-      daemonNiceLevel = mkOption {
-        type = types.int;
-        default = 0;
+      daemonCPUSchedPolicy = mkOption {
+        type = types.enum ["other" "batch" "idle"];
+        default = "other";
+        example = "batch";
         description = ''
-          Nix daemon process priority. This priority propagates to build processes.
-          0 is the default Unix process priority, 19 is the lowest.
-        '';
+          Nix daemon process CPU scheduling policy. This policy propagates to
+          build processes. other is the default scheduling policy for regular
+          tasks. The batch policy is similar to other, but optimised for
+          non-interactive tasks. idle is for extremely low-priority tasks
+          that should only be run when no other task requires CPU time.
+
+          Please note that while using the idle policy may greatly improve
+          responsiveness of a system performing expensive builds, it may also
+          slow down and potentially starve crucial configuration updates
+          during load.
+      '';
+      };
+
+      daemonIOSchedClass = mkOption {
+        type = types.enum ["best-effort" "idle"];
+        default = "best-effort";
+        example = "idle";
+        description = ''
+          Nix daemon process I/O scheduling class. This class propagates to
+          build processes. best-effort is the default class for regular tasks.
+          The idle class is for extremely low-priority tasks that should only
+          perform I/O when no other task does.
+
+          Please note that while using the idle scheduling class can improve
+          responsiveness of a system performing expensive builds, it might also
+          slow down or starve crucial configuration updates during load.
+      '';
       };
 
-      daemonIONiceLevel = mkOption {
+      daemonIOSchedPriority = mkOption {
         type = types.int;
         default = 0;
+        example = 1;
         description = ''
-          Nix daemon process I/O priority. This priority propagates to build processes.
-          0 is the default Unix process I/O priority, 7 is the lowest.
-        '';
+          Nix daemon process I/O scheduling priority. This priority propagates
+          to build processes. The supported priorities depend on the
+          scheduling policy: With idle, priorities are not used in scheduling
+          decisions. best-effort supports values in the range 0 (high) to 7
+          (low).
+      '';
       };
 
       buildMachines = mkOption {
@@ -460,7 +498,7 @@ in
               flake = mkOption {
                 type = types.nullOr types.attrs;
                 default = null;
-                example = literalExample "nixpkgs";
+                example = literalExpression "nixpkgs";
                 description = ''
                   The flake input to which <option>from></option> is to be rewritten.
                 '';
@@ -499,7 +537,7 @@ in
 
   ###### implementation
 
-  config = {
+  config = mkIf cfg.enable {
 
     nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
     nix.binaryCaches = [ "https://cache.nixos.org/" ];
@@ -534,6 +572,22 @@ in
             + "\n"
           ) cfg.buildMachines;
       };
+    assertions =
+      let badMachine = m: m.system == null && m.systems == [];
+      in [
+        {
+          assertion = !(builtins.any badMachine cfg.buildMachines);
+          message = ''
+            At least one system type (via <varname>system</varname> or
+              <varname>systems</varname>) must be set for every build machine.
+              Invalid machine specifications:
+          '' + "      " +
+          (builtins.concatStringsSep "\n      "
+            (builtins.map (m: m.hostName)
+              (builtins.filter (badMachine) cfg.buildMachines)));
+        }
+      ];
+
 
     systemd.packages = [ nix ];
 
@@ -550,8 +604,9 @@ in
         unitConfig.RequiresMountsFor = "/nix/store";
 
         serviceConfig =
-          { Nice = cfg.daemonNiceLevel;
-            IOSchedulingPriority = cfg.daemonIONiceLevel;
+          { CPUSchedulingPolicy = cfg.daemonCPUSchedPolicy;
+            IOSchedulingClass = cfg.daemonIOSchedClass;
+            IOSchedulingPriority = cfg.daemonIOSchedPriority;
             LimitNOFILE = 4096;
           };
 
diff --git a/nixos/modules/services/misc/nix-ssh-serve.nix b/nixos/modules/services/misc/nix-ssh-serve.nix
index 1764c6d79649c..d5c64fdb26472 100644
--- a/nixos/modules/services/misc/nix-ssh-serve.nix
+++ b/nixos/modules/services/misc/nix-ssh-serve.nix
@@ -44,9 +44,11 @@ in {
 
     users.users.nix-ssh = {
       description = "Nix SSH store user";
-      uid = config.ids.uids.nix-ssh;
+      isSystemUser = true;
+      group = "nix-ssh";
       useDefaultShell = true;
     };
+    users.groups.nix-ssh = {};
 
     services.openssh.enable = true;
 
diff --git a/nixos/modules/services/misc/nzbhydra2.nix b/nixos/modules/services/misc/nzbhydra2.nix
index c396b4b8f6e94..500c40f117dda 100644
--- a/nixos/modules/services/misc/nzbhydra2.nix
+++ b/nixos/modules/services/misc/nzbhydra2.nix
@@ -25,7 +25,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.nzbhydra2;
-        defaultText = "pkgs.nzbhydra2";
+        defaultText = literalExpression "pkgs.nzbhydra2";
         description = "NZBHydra2 package to use.";
       };
     };
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index 7129ac69527fa..cd846d3f268da 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -68,8 +68,8 @@ in
       plugins = mkOption {
         type = types.functionTo (types.listOf types.package);
         default = plugins: [];
-        defaultText = "plugins: []";
-        example = literalExample "plugins: with plugins; [ themeify stlviewer ]";
+        defaultText = literalExpression "plugins: []";
+        example = literalExpression "plugins: with plugins; [ themeify stlviewer ]";
         description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
       };
 
diff --git a/nixos/modules/services/misc/owncast.nix b/nixos/modules/services/misc/owncast.nix
new file mode 100644
index 0000000000000..0852335238fd4
--- /dev/null
+++ b/nixos/modules/services/misc/owncast.nix
@@ -0,0 +1,98 @@
+{ lib, pkgs, config, ... }:
+with lib;
+let cfg = config.services.owncast;
+in {
+
+  options.services.owncast = {
+
+    enable = mkEnableOption "owncast";
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/owncast";
+      description = ''
+        The directory where owncast stores its data files. If left as the default value this directory will automatically be created before the owncast server starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Open the appropriate ports in the firewall for owncast.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "owncast";
+      description = "User account under which owncast runs.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "owncast";
+      description = "Group under which owncast runs.";
+    };
+
+    listen = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      example = "0.0.0.0";
+      description = "The IP address to bind the owncast web server to.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = ''
+        TCP port where owncast web-gui listens.
+      '';
+    };
+
+    rtmp-port = mkOption {
+      type = types.port;
+      default = 1935;
+      description = ''
+        TCP port where owncast rtmp service listens.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.owncast = {
+      description = "A self-hosted live video and web chat server";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = mkMerge [
+        {
+          User = cfg.user;
+          Group = cfg.group;
+          WorkingDirectory = cfg.dataDir;
+          ExecStart = "${pkgs.owncast}/bin/owncast -webserverport ${toString cfg.port} -rtmpport ${toString cfg.rtmp-port} -webserverip ${cfg.listen}";
+          Restart = "on-failure";
+        }
+        (mkIf (cfg.dataDir == "/var/lib/owncast") {
+          StateDirectory = "owncast";
+        })
+      ];
+    };
+
+    users.users = mkIf (cfg.user == "owncast") {
+      owncast = {
+        isSystemUser = true;
+        group = cfg.group;
+        description = "owncast system user";
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "owncast") { owncast = { }; };
+
+    networking.firewall =
+      mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.rtmp-port ] ++ optional (cfg.listen != "127.0.0.1") cfg.port; };
+
+  };
+  meta = { maintainers = with lib.maintainers; [ MayNiklas ]; };
+}
diff --git a/nixos/modules/services/misc/paperless-ng.nix b/nixos/modules/services/misc/paperless-ng.nix
index 4b7087e17f96d..db8082f072c3b 100644
--- a/nixos/modules/services/misc/paperless-ng.nix
+++ b/nixos/modules/services/misc/paperless-ng.nix
@@ -107,14 +107,14 @@ in
     mediaDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/media";
-      defaultText = "\${dataDir}/consume";
+      defaultText = literalExpression ''"''${dataDir}/media"'';
       description = "Directory to store the Paperless documents.";
     };
 
     consumptionDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/consume";
-      defaultText = "\${dataDir}/consume";
+      defaultText = literalExpression ''"''${dataDir}/consume"'';
       description = "Directory from which new documents are imported.";
     };
 
@@ -167,7 +167,7 @@ in
         See <link xlink:href="https://paperless-ng.readthedocs.io/en/latest/configuration.html">the documentation</link>
         for available options.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         {
           PAPERLESS_OCR_LANGUAGE = "deu+eng";
         }
@@ -183,7 +183,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.paperless-ng;
-      defaultText = "pkgs.paperless-ng";
+      defaultText = literalExpression "pkgs.paperless-ng";
       description = "The Paperless package to use.";
     };
   };
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index 7efadf1b9bb1a..2ae4e80d5c3fd 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -65,10 +65,33 @@ in
         '';
       };
 
+      extraScanners = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of paths to extra scanners to install in Plex's scanners
+          directory.
+
+          Every time the systemd unit for Plex starts up, all of the symlinks
+          in Plex's scanners directory will be cleared and this module will
+          symlink all of the paths specified here to that directory.
+        '';
+        example = literalExpression ''
+          [
+            (fetchFromGitHub {
+              owner = "ZeroQI";
+              repo = "Absolute-Series-Scanner";
+              rev = "773a39f502a1204b0b0255903cee4ed02c46fde0";
+              sha256 = "4l+vpiDdC8L/EeJowUgYyB3JPNTZ1sauN8liFAcK+PY=";
+            })
+          ]
+        '';
+      };
+
       package = mkOption {
         type = types.package;
         default = pkgs.plex;
-        defaultText = "pkgs.plex";
+        defaultText = literalExpression "pkgs.plex";
         description = ''
           The Plex package to use. Plex subscribers may wish to use their own
           package here, pointing to subscriber-only server versions.
@@ -113,6 +136,7 @@ in
         # Configuration for our FHS userenv script
         PLEX_DATADIR=cfg.dataDir;
         PLEX_PLUGINS=concatMapStringsSep ":" builtins.toString cfg.extraPlugins;
+        PLEX_SCANNERS=concatMapStringsSep ":" builtins.toString cfg.extraScanners;
 
         # The following variables should be set by the FHS userenv script:
         #   PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR
diff --git a/nixos/modules/services/misc/prowlarr.nix b/nixos/modules/services/misc/prowlarr.nix
new file mode 100644
index 0000000000000..ef820b4022d5b
--- /dev/null
+++ b/nixos/modules/services/misc/prowlarr.nix
@@ -0,0 +1,41 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prowlarr;
+
+in
+{
+  options = {
+    services.prowlarr = {
+      enable = mkEnableOption "Prowlarr";
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the Prowlarr web interface.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.prowlarr = {
+      description = "Prowlarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        StateDirectory = "prowlarr";
+        ExecStart = "${pkgs.prowlarr}/bin/Prowlarr -nobrowser -data=/var/lib/prowlarr";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 9696 ];
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 66c8e558fb041..696b8d1a25d9c 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkBefore mkDefault mkEnableOption mkIf mkOption mkRemovedOptionModule types;
-  inherit (lib) concatStringsSep literalExample mapAttrsToList;
+  inherit (lib) concatStringsSep literalExpression mapAttrsToList;
   inherit (lib) optional optionalAttrs optionalString;
 
   cfg = config.services.redmine;
@@ -54,8 +54,9 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.redmine;
+        defaultText = literalExpression "pkgs.redmine";
         description = "Which Redmine package to use.";
-        example = "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
+        example = literalExpression "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
       };
 
       user = mkOption {
@@ -90,7 +91,7 @@ in
           <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
           for details.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             email_delivery = {
               delivery_method = "smtp";
@@ -112,7 +113,7 @@ in
           See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
           for details.
         '';
-        example = literalExample ''
+        example = ''
           config.logger.level = Logger::DEBUG
         '';
       };
@@ -121,7 +122,7 @@ in
         type = types.attrsOf types.path;
         default = {};
         description = "Set of themes.";
-        example = literalExample ''
+        example = literalExpression ''
           {
             dkuk-redmine_alex_skin = builtins.fetchurl {
               url = "https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip";
@@ -135,7 +136,7 @@ in
         type = types.attrsOf types.path;
         default = {};
         description = "Set of plugins.";
-        example = literalExample ''
+        example = literalExpression ''
           {
             redmine_env_auth = builtins.fetchurl {
               url = "https://github.com/Intera/redmine_env_auth/archive/0.6.zip";
@@ -162,7 +163,7 @@ in
         port = mkOption {
           type = types.int;
           default = if cfg.database.type == "postgresql" then 5432 else 3306;
-          defaultText = "3306";
+          defaultText = literalExpression "3306";
           description = "Database host port.";
         };
 
@@ -194,7 +195,7 @@ in
             if mysqlLocal then "/run/mysqld/mysqld.sock"
             else if pgsqlLocal then "/run/postgresql"
             else null;
-          defaultText = "/run/mysqld/mysqld.sock";
+          defaultText = literalExpression "/run/mysqld/mysqld.sock";
           example = "/run/mysqld/mysqld.sock";
           description = "Path to the unix socket file to use for authentication.";
         };
diff --git a/nixos/modules/services/misc/ripple-data-api.nix b/nixos/modules/services/misc/ripple-data-api.nix
index 9fab462f7e3b4..93eba98b7d309 100644
--- a/nixos/modules/services/misc/ripple-data-api.nix
+++ b/nixos/modules/services/misc/ripple-data-api.nix
@@ -187,7 +187,9 @@ in {
 
     users.users.ripple-data-api =
       { description = "Ripple data api user";
-        uid = config.ids.uids.ripple-data-api;
+        isSystemUser = true;
+        group = "ripple-data-api";
       };
+    users.groups.ripple-data-api = {};
   };
 }
diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix
index 2fce3b9dc94c7..9c66df2fce1c3 100644
--- a/nixos/modules/services/misc/rippled.nix
+++ b/nixos/modules/services/misc/rippled.nix
@@ -210,7 +210,7 @@ in
         description = "Which rippled package to use.";
         type = types.package;
         default = pkgs.rippled;
-        defaultText = "pkgs.rippled";
+        defaultText = literalExpression "pkgs.rippled";
       };
 
       ports = mkOption {
@@ -407,12 +407,14 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users.rippled =
-      { description = "Ripple server user";
-        uid = config.ids.uids.rippled;
+    users.users.rippled = {
+        description = "Ripple server user";
+        isSystemUser = true;
+        group = "rippled";
         home = cfg.databasePath;
         createHome = true;
       };
+    users.groups.rippled = {};
 
     systemd.services.rippled = {
       after = [ "network.target" ];
diff --git a/nixos/modules/services/misc/safeeyes.nix b/nixos/modules/services/misc/safeeyes.nix
index 1e748195e41aa..638218d8bb00c 100644
--- a/nixos/modules/services/misc/safeeyes.nix
+++ b/nixos/modules/services/misc/safeeyes.nix
@@ -26,12 +26,16 @@ in
 
   config = mkIf cfg.enable {
 
+    environment.systemPackages = [ pkgs.safeeyes ];
+
     systemd.user.services.safeeyes = {
       description = "Safeeyes";
 
       wantedBy = [ "graphical-session.target" ];
       partOf   = [ "graphical-session.target" ];
 
+      path = [ pkgs.alsa-utils ];
+
       startLimitIntervalSec = 350;
       startLimitBurst = 10;
       serviceConfig = {
diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix
index a32dbfa3108f9..8e871309c98e8 100644
--- a/nixos/modules/services/misc/sickbeard.nix
+++ b/nixos/modules/services/misc/sickbeard.nix
@@ -24,7 +24,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.sickbeard;
-        example = literalExample "pkgs.sickrage";
+        defaultText = literalExpression "pkgs.sickbeard";
+        example = literalExpression "pkgs.sickrage";
         description =''
           Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal>
           as an alternative to SickBeard
@@ -85,7 +86,7 @@ in
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${sickbeard}/SickBeard.py --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}";
+        ExecStart = "${sickbeard}/bin/${sickbeard.pname} --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}";
       };
     };
   };
diff --git a/nixos/modules/services/misc/signald.nix b/nixos/modules/services/misc/signald.nix
new file mode 100644
index 0000000000000..4cd34e4326d77
--- /dev/null
+++ b/nixos/modules/services/misc/signald.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.signald;
+  dataDir = "/var/lib/signald";
+  defaultUser = "signald";
+in
+{
+  options.services.signald = {
+    enable = mkEnableOption "the signald service";
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = "User under which signald runs.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = "Group under which signald runs.";
+    };
+
+    socketPath = mkOption {
+      type = types.str;
+      default = "/run/signald/signald.sock";
+      description = "Path to the signald socket";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == defaultUser) {
+      ${defaultUser} = { };
+    };
+
+    systemd.services.signald = {
+      description = "A daemon for interacting with the Signal Private Messenger";
+      wants = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${pkgs.signald}/bin/signald -d ${dataDir} -s ${cfg.socketPath}";
+        Restart = "on-failure";
+        StateDirectory = "signald";
+        RuntimeDirectory = "signald";
+        StateDirectoryMode = "0750";
+        RuntimeDirectoryMode = "0750";
+
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/hosts"
+          "-/etc/localtime"
+        ];
+        CapabilityBoundingSet = "";
+        # ProtectClock= adds DeviceAllow=char-rtc r
+        DeviceAllow = "";
+        # Use a static user so other applications can access the files
+        #DynamicUser = true;
+        LockPersonality = true;
+        # Needed for java
+        #MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        # Needs network access
+        #PrivateNetwork = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        # Would re-mount paths ignored by temporary root
+        #ProtectSystem = "strict";
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
+        TemporaryFileSystem = "/:ro";
+        # Does not work well with the temporary root
+        #UMask = "0066";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index a821b9b6bf652..3c3f6c4d641b2 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -9,6 +9,14 @@ in
 {
   options.services.snapper = {
 
+    snapshotRootOnBoot = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to snapshot root on boot
+      '';
+    };
+
     snapshotInterval = mkOption {
       type = types.str;
       default = "hourly";
@@ -43,16 +51,18 @@ in
 
     configs = mkOption {
       default = { };
-      example = literalExample {
-        home = {
-          subvolume = "/home";
-          extraConfig = ''
-            ALLOW_USERS="alice"
-            TIMELINE_CREATE=yes
-            TIMELINE_CLEANUP=yes
-          '';
-        };
-      };
+      example = literalExpression ''
+        {
+          home = {
+            subvolume = "/home";
+            extraConfig = '''
+              ALLOW_USERS="alice"
+              TIMELINE_CREATE=yes
+              TIMELINE_CLEANUP=yes
+            ''';
+          };
+        }
+      '';
 
       description = ''
         Subvolume configuration
@@ -130,20 +140,22 @@ in
         Type = "dbus";
         BusName = "org.opensuse.Snapper";
         ExecStart = "${pkgs.snapper}/bin/snapperd";
+        CapabilityBoundingSet = "CAP_DAC_OVERRIDE CAP_FOWNER CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_SYS_ADMIN CAP_SYS_MODULE CAP_IPC_LOCK CAP_SYS_NICE";
+        LockPersonality = true;
+        NoNewPrivileges = false;
+        PrivateNetwork = true;
+        ProtectHostname = true;
+        RestrictAddressFamilies = "AF_UNIX";
+        RestrictRealtime = true;
       };
     };
 
     systemd.services.snapper-timeline = {
       description = "Timeline of Snapper Snapshots";
       inherit documentation;
+      requires = [ "local-fs.target" ];
       serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
-    };
-
-    systemd.timers.snapper-timeline = {
-      description = "Timeline of Snapper Snapshots";
-      inherit documentation;
-      wantedBy = [ "basic.target" ];
-      timerConfig.OnCalendar = cfg.snapshotInterval;
+      startAt = cfg.snapshotInterval;
     };
 
     systemd.services.snapper-cleanup = {
@@ -155,10 +167,21 @@ in
     systemd.timers.snapper-cleanup = {
       description = "Cleanup of Snapper Snapshots";
       inherit documentation;
-      wantedBy = [ "basic.target" ];
+      wantedBy = [ "timers.target" ];
+      requires = [ "local-fs.target" ];
       timerConfig.OnBootSec = "10m";
       timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
     };
+
+    systemd.services.snapper-boot = lib.optionalAttrs cfg.snapshotRootOnBoot {
+      description = "Take snapper snapshot of root on boot";
+      inherit documentation;
+      serviceConfig.ExecStart = "${pkgs.snapper}/bin/snapper --config root create --cleanup-algorithm number --description boot";
+      serviceConfig.type = "oneshot";
+      requires = [ "local-fs.target" ];
+      wantedBy = [ "multi-user.target" ];
+      unitConfig.ConditionPathExists = "/etc/snapper/configs/root";
+    };
+
   });
 }
-
diff --git a/nixos/modules/services/misc/sourcehut/builds.nix b/nixos/modules/services/misc/sourcehut/builds.nix
index e446f08284f7c..f806e8c51b99e 100644
--- a/nixos/modules/services/misc/sourcehut/builds.nix
+++ b/nixos/modules/services/misc/sourcehut/builds.nix
@@ -54,7 +54,7 @@ in
     images = mkOption {
       type = types.attrsOf (types.attrsOf (types.attrsOf types.package));
       default = { };
-      example = lib.literalExample ''(let
+      example = lib.literalExpression ''(let
           # Pinning unstable to allow usage with flakes and limit rebuilds.
           pkgs_unstable = builtins.fetchGit {
               url = "https://github.com/NixOS/nixpkgs";
diff --git a/nixos/modules/services/misc/sourcehut/git.nix b/nixos/modules/services/misc/sourcehut/git.nix
index 99b9aec061239..2653d77876dca 100644
--- a/nixos/modules/services/misc/sourcehut/git.nix
+++ b/nixos/modules/services/misc/sourcehut/git.nix
@@ -49,7 +49,8 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.git;
-      example = literalExample "pkgs.gitFull";
+      defaultText = literalExpression "pkgs.git";
+      example = literalExpression "pkgs.gitFull";
       description = ''
         Git package for git.sr.ht. This can help silence collisions.
       '';
diff --git a/nixos/modules/services/misc/ssm-agent.nix b/nixos/modules/services/misc/ssm-agent.nix
index c29d03d199bf4..4ae596ade1745 100644
--- a/nixos/modules/services/misc/ssm-agent.nix
+++ b/nixos/modules/services/misc/ssm-agent.nix
@@ -23,7 +23,7 @@ in {
       type = types.path;
       description = "The SSM agent package to use";
       default = pkgs.ssm-agent.override { overrideEtc = false; };
-      defaultText = "pkgs.ssm-agent.override { overrideEtc = false; }";
+      defaultText = literalExpression "pkgs.ssm-agent.override { overrideEtc = false; }";
     };
   };
 
diff --git a/nixos/modules/services/misc/subsonic.nix b/nixos/modules/services/misc/subsonic.nix
index e17a98a5e1deb..98b85918ad180 100644
--- a/nixos/modules/services/misc/subsonic.nix
+++ b/nixos/modules/services/misc/subsonic.nix
@@ -93,6 +93,7 @@ let cfg = config.services.subsonic; in {
       transcoders = mkOption {
         type = types.listOf types.path;
         default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
+        defaultText = literalExpression ''[ "''${pkgs.ffmpeg.bin}/bin/ffmpeg" ]'';
         description = ''
           List of paths to transcoder executables that should be accessible
           from Subsonic. Symlinks will be created to each executable inside
@@ -108,7 +109,7 @@ let cfg = config.services.subsonic; in {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       script = ''
-        ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
+        ${pkgs.jre8}/bin/java -Xmx${toString cfg.maxMemory}m \
           -Dsubsonic.home=${cfg.home} \
           -Dsubsonic.host=${cfg.listenAddress} \
           -Dsubsonic.port=${toString cfg.port} \
diff --git a/nixos/modules/services/misc/tautulli.nix b/nixos/modules/services/misc/tautulli.nix
index aded33629f1c2..9a972b291225c 100644
--- a/nixos/modules/services/misc/tautulli.nix
+++ b/nixos/modules/services/misc/tautulli.nix
@@ -47,7 +47,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.tautulli;
-        defaultText = "pkgs.tautulli";
+        defaultText = literalExpression "pkgs.tautulli";
         description = ''
           The Tautulli package to use.
         '';
diff --git a/nixos/modules/services/misc/tp-auto-kbbl.nix b/nixos/modules/services/misc/tp-auto-kbbl.nix
new file mode 100644
index 0000000000000..59018f7f81ffa
--- /dev/null
+++ b/nixos/modules/services/misc/tp-auto-kbbl.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.tp-auto-kbbl;
+
+in {
+  meta.maintainers = with maintainers; [ sebtm ];
+
+  options = {
+    services.tp-auto-kbbl = {
+      enable = mkEnableOption "Auto toggle keyboard back-lighting on Thinkpads (and maybe other laptops) for Linux";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.tp-auto-kbbl;
+        defaultText = literalExpression "pkgs.tp-auto-kbbl";
+        description = "Package providing <command>tp-auto-kbbl</command>.";
+      };
+
+      arguments = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          List of arguments appended to <literal>./tp-auto-kbbl --device [device] [arguments]</literal>
+        '';
+      };
+
+      device = mkOption {
+        type = types.str;
+        default = "/dev/input/event0";
+        description = "Device watched for activities.";
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.tp-auto-kbbl = {
+      serviceConfig = {
+        ExecStart = concatStringsSep " "
+          ([ "${cfg.package}/bin/tp-auto-kbbl" "--device ${cfg.device}" ] ++ cfg.arguments);
+        Restart = "always";
+        Type = "simple";
+      };
+
+      unitConfig = {
+        Description = "Auto toggle keyboard backlight";
+        Documentation = "https://github.com/saibotd/tp-auto-kbbl";
+        After = [ "dbus.service" ];
+      };
+
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/uhub.nix b/nixos/modules/services/misc/uhub.nix
index da2613e6db173..0d0a8c2a4cb81 100644
--- a/nixos/modules/services/misc/uhub.nix
+++ b/nixos/modules/services/misc/uhub.nix
@@ -50,7 +50,7 @@ in {
                 options = {
                   plugin = mkOption {
                     type = path;
-                    example = literalExample
+                    example = literalExpression
                       "$${pkgs.uhub}/plugins/mod_auth_sqlite.so";
                     description = "Path to plugin file.";
                   };
diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix
index b71250f62e0f3..7a4c4dca2ac99 100644
--- a/nixos/modules/services/misc/weechat.nix
+++ b/nixos/modules/services/misc/weechat.nix
@@ -21,11 +21,10 @@ in
     };
     binary = mkOption {
       type = types.path;
-      description = "Binary to execute (by default \${weechat}/bin/weechat).";
-      example = literalExample ''
-        ''${pkgs.weechat}/bin/weechat-headless
-      '';
+      description = "Binary to execute.";
       default = "${pkgs.weechat}/bin/weechat";
+      defaultText = literalExpression ''"''${pkgs.weechat}/bin/weechat"'';
+      example = literalExpression ''"''${pkgs.weechat}/bin/weechat-headless"'';
     };
   };
 
@@ -52,7 +51,12 @@ in
       wants = [ "network.target" ];
     };
 
-    security.wrappers.screen.source = "${pkgs.screen}/bin/screen";
+    security.wrappers.screen =
+      { setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.screen}/bin/screen";
+      };
   };
 
   meta.doc = ./weechat.xml;
diff --git a/nixos/modules/services/misc/xmr-stak.nix b/nixos/modules/services/misc/xmr-stak.nix
index a87878c31e0d5..9256e9ae01cb9 100644
--- a/nixos/modules/services/misc/xmr-stak.nix
+++ b/nixos/modules/services/misc/xmr-stak.nix
@@ -29,7 +29,7 @@ in
       configFiles = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "config.txt" = '''
               "verbose_level" : 4,
diff --git a/nixos/modules/services/misc/xmrig.nix b/nixos/modules/services/misc/xmrig.nix
new file mode 100644
index 0000000000000..cf01bb119e894
--- /dev/null
+++ b/nixos/modules/services/misc/xmrig.nix
@@ -0,0 +1,75 @@
+{ config, pkgs, lib, ... }:
+
+
+let
+  cfg = config.services.xmrig;
+
+  json = pkgs.formats.json { };
+  configFile = json.generate "config.json" cfg.settings;
+in
+
+with lib;
+
+{
+  options = {
+    services.xmrig = {
+      enable = mkEnableOption "XMRig Mining Software";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.xmrig;
+        example = literalExpression "pkgs.xmrig-mo";
+        description = "XMRig package to use.";
+      };
+
+      settings = mkOption {
+        default = { };
+        type = json.type;
+        example = literalExpression ''
+          {
+            autosave = true;
+            cpu = true;
+            opencl = false;
+            cuda = false;
+            pools = [
+              {
+                url = "pool.supportxmr.com:443";
+                user = "your-wallet";
+                keepalive = true;
+                tls = true;
+              }
+            ]
+          }
+        '';
+        description = ''
+          XMRig configuration. Refer to
+          <link xlink:href="https://xmrig.com/docs/miner/config"/>
+          for details on supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "msr" ];
+
+    systemd.services.xmrig = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "XMRig Mining Software Service";
+      serviceConfig = {
+        ExecStartPre = "${cfg.package}/bin/xmrig --config=${configFile} --dry-run";
+        ExecStart = "${cfg.package}/bin/xmrig --config=${configFile}";
+        # https://xmrig.com/docs/miner/randomx-optimization-guide/msr
+        # If you use recent XMRig with root privileges (Linux) or admin
+        # privileges (Windows) the miner configure all MSR registers
+        # automatically.
+        DynamicUser = lib.mkDefault false;
+      };
+    };
+  };
+
+  meta = with lib; {
+    maintainers = with maintainers; [ ratsclub ];
+  };
+}
diff --git a/nixos/modules/services/misc/zigbee2mqtt.nix b/nixos/modules/services/misc/zigbee2mqtt.nix
index 4458da1346b7d..b378d9f362fe3 100644
--- a/nixos/modules/services/misc/zigbee2mqtt.nix
+++ b/nixos/modules/services/misc/zigbee2mqtt.nix
@@ -25,7 +25,7 @@ in
       default = pkgs.zigbee2mqtt.override {
         dataDir = cfg.dataDir;
       };
-      defaultText = literalExample ''
+      defaultText = literalExpression ''
         pkgs.zigbee2mqtt {
           dataDir = services.zigbee2mqtt.dataDir
         }
@@ -42,7 +42,7 @@ in
     settings = mkOption {
       type = format.type;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           homeassistant = config.services.home-assistant.enable;
           permit_join = true;
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index d9d34b7fac9b3..378da7b87442a 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -366,5 +366,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ peterhoeg ];
+  meta.maintainers = with lib.maintainers; [ ];
 }
diff --git a/nixos/modules/services/misc/zookeeper.nix b/nixos/modules/services/misc/zookeeper.nix
index 1d12e81a9eca8..3809a93a61e18 100644
--- a/nixos/modules/services/misc/zookeeper.nix
+++ b/nixos/modules/services/misc/zookeeper.nix
@@ -110,7 +110,7 @@ in {
     package = mkOption {
       description = "The zookeeper package to use";
       default = pkgs.zookeeper;
-      defaultText = "pkgs.zookeeper";
+      defaultText = literalExpression "pkgs.zookeeper";
       type = types.package;
     };
 
@@ -148,9 +148,11 @@ in {
     };
 
     users.users.zookeeper = {
-      uid = config.ids.uids.zookeeper;
+      isSystemUser = true;
+      group = "zookeeper";
       description = "Zookeeper daemon user";
       home = cfg.dataDir;
     };
+    users.groups.zookeeper = {};
   };
 }
diff --git a/nixos/modules/services/monitoring/alerta.nix b/nixos/modules/services/monitoring/alerta.nix
index 7c6eff713cb12..a73d94001f710 100644
--- a/nixos/modules/services/monitoring/alerta.nix
+++ b/nixos/modules/services/monitoring/alerta.nix
@@ -32,7 +32,6 @@ in
     bind = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      example = literalExample "0.0.0.0";
       description = "Address to bind to. The default is to bind to all addresses";
     };
 
@@ -46,20 +45,17 @@ in
       type = types.str;
       description = "URL of the MongoDB or PostgreSQL database to connect to";
       default = "mongodb://localhost";
-      example = "mongodb://localhost";
     };
 
     databaseName = mkOption {
       type = types.str;
       description = "Name of the database instance to connect to";
       default = "monitoring";
-      example = "monitoring";
     };
 
     corsOrigins = mkOption {
       type = types.listOf types.str;
       description = "List of URLs that can access the API for Cross-Origin Resource Sharing (CORS)";
-      example = [ "http://localhost" "http://localhost:5000" ];
       default = [ "http://localhost" "http://localhost:5000" ];
     };
 
diff --git a/nixos/modules/services/monitoring/arbtt.nix b/nixos/modules/services/monitoring/arbtt.nix
index b41a3c7b50161..94eead220aed6 100644
--- a/nixos/modules/services/monitoring/arbtt.nix
+++ b/nixos/modules/services/monitoring/arbtt.nix
@@ -18,8 +18,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.haskellPackages.arbtt;
-        defaultText = "pkgs.haskellPackages.arbtt";
-        example = literalExample "pkgs.haskellPackages.arbtt";
+        defaultText = literalExpression "pkgs.haskellPackages.arbtt";
         description = ''
           The package to use for the arbtt binaries.
         '';
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index 04e9da1c81a34..4b278b9c200b9 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -33,8 +33,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.bosun;
-        defaultText = "pkgs.bosun";
-        example = literalExample "pkgs.bosun";
+        defaultText = literalExpression "pkgs.bosun";
         description = ''
           bosun binary to use.
         '';
diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix
index da051dbe4655b..dfbf07efcaea9 100644
--- a/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixos/modules/services/monitoring/cadvisor.nix
@@ -111,6 +111,8 @@ in {
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" "docker.service" "influxdb.service" ];
 
+        path = optionals config.boot.zfs.enabled [ pkgs.zfs ];
+
         postStart = mkBefore ''
           until ${pkgs.curl.bin}/bin/curl -s -o /dev/null 'http://${cfg.listenAddress}:${toString cfg.port}/containers/'; do
             sleep 1;
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index ef3663c62e048..6af04d22f0f70 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -45,7 +45,7 @@ in {
 
     package = mkOption {
       default = pkgs.collectd;
-      defaultText = "pkgs.collectd";
+      defaultText = literalExpression "pkgs.collectd";
       description = ''
         Which collectd package to use.
       '';
@@ -57,7 +57,7 @@ in {
       description = ''
         Build a minimal collectd package with only the configured `services.collectd.plugins`
       '';
-      type = types.bool;
+      type = bool;
     };
 
     user = mkOption {
@@ -98,7 +98,7 @@ in {
       description = ''
         Attribute set of plugin names to plugin config segments
       '';
-      type = types.attrsOf types.str;
+      type = attrsOf lines;
     };
 
     extraConfig = mkOption {
diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index b25a53435d069..6d9d1ef973a44 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -51,7 +51,7 @@ in {
   options.services.datadog-agent = {
     enable = mkOption {
       description = ''
-        Whether to enable the datadog-agent v6 monitoring service
+        Whether to enable the datadog-agent v7 monitoring service
       '';
       default = false;
       type = types.bool;
@@ -59,9 +59,9 @@ in {
 
     package = mkOption {
       default = pkgs.datadog-agent;
-      defaultText = "pkgs.datadog-agent";
+      defaultText = literalExpression "pkgs.datadog-agent";
       description = ''
-        Which DataDog v6 agent package to use. Note that the provided
+        Which DataDog v7 agent package to use. Note that the provided
         package is expected to have an overridable `pythonPackages`-attribute
         which configures the Python environment with the Datadog
         checks.
@@ -135,9 +135,11 @@ in {
         package set must be provided.
       '';
 
-      example = {
-        ntp = (pythonPackages: [ pythonPackages.ntplib ]);
-      };
+      example = literalExpression ''
+        {
+          ntp = pythonPackages: [ pythonPackages.ntplib ];
+        }
+      '';
     };
 
     extraConfig = mkOption {
@@ -274,7 +276,7 @@ in {
         path = [ ];
         script = ''
           export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
-          ${pkgs.datadog-process-agent}/bin/agent --config /etc/datadog-agent/datadog.yaml
+          ${pkgs.datadog-process-agent}/bin/process-agent --config /etc/datadog-agent/datadog.yaml
         '';
       });
 
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index 893c15d568bdb..e40d78f538faf 100644
--- a/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -41,8 +41,9 @@ in {
 
     templateDir = mkOption {
       description = "Optional template directory to use custom tex templates";
-      default = "${pkgs.grafana_reporter}";
-      type = types.str;
+      default = pkgs.grafana_reporter;
+      defaultText = literalExpression "pkgs.grafana_reporter";
+      type = types.either types.str types.path;
     };
   };
 
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index d46e38e82af14..5067047e9690e 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -330,13 +330,14 @@ in {
     staticRootPath = mkOption {
       description = "Root path for static assets.";
       default = "${cfg.package}/share/grafana/public";
+      defaultText = literalExpression ''"''${package}/share/grafana/public"'';
       type = types.str;
     };
 
     package = mkOption {
       description = "Package to use.";
       default = pkgs.grafana;
-      defaultText = "pkgs.grafana";
+      defaultText = literalExpression "pkgs.grafana";
       type = types.package;
     };
 
@@ -344,7 +345,7 @@ in {
       type = with types; nullOr (listOf path);
       default = null;
       description = "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
-      example = literalExample "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
+      example = literalExpression "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
       # Make sure each plugin is added only once; otherwise building
       # the link farm fails, since the same path is added multiple
       # times.
@@ -676,15 +677,13 @@ in {
         RuntimeDirectory = "grafana";
         RuntimeDirectoryMode = "0755";
         # Hardening
-        CapabilityBoundingSet = [ "" ];
+        AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
         DeviceAllow = [ "" ];
         LockPersonality = true;
-        MemoryDenyWriteExecute = true;
         NoNewPrivileges = true;
         PrivateDevices = true;
         PrivateTmp = true;
-        PrivateUsers = true;
-        ProcSubset = "pid";
         ProtectClock = true;
         ProtectControlGroups = true;
         ProtectHome = true;
@@ -700,6 +699,8 @@ in {
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
         SystemCallArchitectures = "native";
+        # Upstream grafana is not setting SystemCallFilter for compatibility
+        # reasons, see https://github.com/grafana/grafana/pull/40176
         SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
         UMask = "0027";
       };
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index 9213748d3c9a1..4690a252c9259 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -132,7 +132,7 @@ in {
       finders = mkOption {
         description = "List of finder plugins to load.";
         default = [];
-        example = literalExample "[ pkgs.python3Packages.influxgraph ]";
+        example = literalExpression "[ pkgs.python3Packages.influxgraph ]";
         type = types.listOf types.package;
       };
 
@@ -160,7 +160,7 @@ in {
       package = mkOption {
         description = "Package to use for graphite api.";
         default = pkgs.python3Packages.graphite_api;
-        defaultText = "pkgs.python3Packages.graphite_api";
+        defaultText = literalExpression "pkgs.python3Packages.graphite_api";
         type = types.package;
       };
 
@@ -335,7 +335,7 @@ in {
           <link xlink:href='https://github.com/scobal/seyren#config' />
         '';
         type = types.attrsOf types.str;
-        example = literalExample ''
+        example = literalExpression ''
           {
             GRAPHITE_USERNAME = "user";
             GRAPHITE_PASSWORD = "pass";
@@ -561,6 +561,7 @@ in {
      ) {
       users.users.graphite = {
         uid = config.ids.uids.graphite;
+        group = "graphite";
         description = "Graphite daemon user";
         home = dataDir;
       };
diff --git a/nixos/modules/services/monitoring/heapster.nix b/nixos/modules/services/monitoring/heapster.nix
index 0a9dfa12eaa51..44f53e1890ac5 100644
--- a/nixos/modules/services/monitoring/heapster.nix
+++ b/nixos/modules/services/monitoring/heapster.nix
@@ -33,7 +33,7 @@ in {
     package = mkOption {
       description = "Package to use by heapster";
       default = pkgs.heapster;
-      defaultText = "pkgs.heapster";
+      defaultText = literalExpression "pkgs.heapster";
       type = types.package;
     };
   };
@@ -50,8 +50,10 @@ in {
     };
 
     users.users.heapster = {
-      uid = config.ids.uids.heapster;
+      isSystemUser = true;
+      group = "heapster";
       description = "Heapster user";
     };
+    users.groups.heapster = {};
   };
 }
diff --git a/nixos/modules/services/monitoring/incron.nix b/nixos/modules/services/monitoring/incron.nix
index dc97af58562e7..2681c35d6a015 100644
--- a/nixos/modules/services/monitoring/incron.nix
+++ b/nixos/modules/services/monitoring/incron.nix
@@ -56,7 +56,7 @@ in
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.rsync ]";
+        example = literalExpression "[ pkgs.rsync ]";
         description = "Extra packages available to the system incrontab.";
       };
 
@@ -71,7 +71,12 @@ in
 
     environment.systemPackages = [ pkgs.incron ];
 
-    security.wrappers.incrontab.source = "${pkgs.incron}/bin/incrontab";
+    security.wrappers.incrontab =
+    { setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${pkgs.incron}/bin/incrontab";
+    };
 
     # incron won't read symlinks
     environment.etc."incron.d/system" = {
diff --git a/nixos/modules/services/monitoring/kapacitor.nix b/nixos/modules/services/monitoring/kapacitor.nix
index 9b4ff3c56124d..a79c647becfc4 100644
--- a/nixos/modules/services/monitoring/kapacitor.nix
+++ b/nixos/modules/services/monitoring/kapacitor.nix
@@ -61,7 +61,6 @@ in
 
     dataDir = mkOption {
       type = types.path;
-      example = "/var/lib/kapacitor";
       default = "/var/lib/kapacitor";
       description = "Location where Kapacitor stores its state";
     };
@@ -75,7 +74,7 @@ in
     bind = mkOption {
       type = types.str;
       default = "";
-      example = literalExample "0.0.0.0";
+      example = "0.0.0.0";
       description = "Address to bind to. The default is to bind to all addresses";
     };
 
@@ -101,7 +100,6 @@ in
       type = types.str;
       description = "Specifies how often to snapshot the task state  (in InfluxDB time units)";
       default = "1m0s";
-      example = "1m0s";
     };
 
     loadDirectory = mkOption {
@@ -136,7 +134,6 @@ in
       url = mkOption {
         description = "The URL to the Alerta REST API";
         default = "http://localhost:5000";
-        example = "http://localhost:5000";
         type = types.str;
       };
 
diff --git a/nixos/modules/services/monitoring/loki.nix b/nixos/modules/services/monitoring/loki.nix
index 51cabaa274a3b..ebac70c30c22e 100644
--- a/nixos/modules/services/monitoring/loki.nix
+++ b/nixos/modules/services/monitoring/loki.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) escapeShellArgs literalExample mkEnableOption mkIf mkOption types;
+  inherit (lib) escapeShellArgs mkEnableOption mkIf mkOption types;
 
   cfg = config.services.loki;
 
@@ -57,7 +57,7 @@ in {
     extraFlags = mkOption {
       type = types.listOf types.str;
       default = [];
-      example = literalExample [ "--server.http-listen-port=3101" ];
+      example = [ "--server.http-listen-port=3101" ];
       description = ''
         Specify a list of additional command line flags,
         which get escaped and are then passed to Loki.
diff --git a/nixos/modules/services/monitoring/mackerel-agent.nix b/nixos/modules/services/monitoring/mackerel-agent.nix
index 7046de9d403cf..aeb6247abd8b1 100644
--- a/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -19,7 +19,6 @@ in {
 
     apiKeyFile = mkOption {
       type = types.path;
-      default = "";
       example = "/run/keys/mackerel-api-key";
       description = ''
         Path to file containing the Mackerel API key. The file should contain a
diff --git a/nixos/modules/services/monitoring/metricbeat.nix b/nixos/modules/services/monitoring/metricbeat.nix
index b285559eaa9b3..e75039daa1079 100644
--- a/nixos/modules/services/monitoring/metricbeat.nix
+++ b/nixos/modules/services/monitoring/metricbeat.nix
@@ -3,7 +3,7 @@
 let
   inherit (lib)
     attrValues
-    literalExample
+    literalExpression
     mkEnableOption
     mkIf
     mkOption
@@ -24,8 +24,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.metricbeat;
-        defaultText = literalExample "pkgs.metricbeat";
-        example = literalExample "pkgs.metricbeat7";
+        defaultText = literalExpression "pkgs.metricbeat";
+        example = literalExpression "pkgs.metricbeat7";
         description = ''
           The metricbeat package to use
         '';
@@ -51,7 +51,6 @@ in
             module = mkOption {
               type = types.str;
               default = name;
-              defaultText = literalExample ''<name>'';
               description = ''
                 The name of the module.
 
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index 1ebf7ee6a761c..4fddb1e37e2e3 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -189,7 +189,7 @@ in
           <literal>/bin</literal>, <literal>/usr/bin</literal>,
           <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
             zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
@@ -220,7 +220,7 @@ in
           <literal>/bin</literal>, <literal>/usr/bin</literal>,
           <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           [
             /src/munin-contrib/plugins/zfs
             /src/munin-contrib/plugins/ssh
@@ -285,9 +285,11 @@ in
           host for cron to succeed. See
           <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />
         '';
-        example = ''
-          [''${config.networking.hostName}]
-          address localhost
+        example = literalExpression ''
+          '''
+            [''${config.networking.hostName}]
+            address localhost
+          '''
         '';
       };
 
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 0afaefe04e181..83020d52fc82b 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -41,7 +41,7 @@ let
     validated =  pkgs.runCommand "nagios-checked.cfg" {preferLocalBuild=true;} ''
       cp ${file} nagios.cfg
       # nagios checks the existence of /var/lib/nagios, but
-      # it does not exists in the build sandbox, so we fake it
+      # it does not exist in the build sandbox, so we fake it
       mkdir lib
       lib=$(readlink -f lib)
       sed -i s@=${nagiosState}@=$lib@ nagios.cfg
@@ -97,13 +97,13 @@ in
           network that you want Nagios to monitor.
         ";
         type = types.listOf types.path;
-        example = literalExample "[ ./objects.cfg ]";
+        example = literalExpression "[ ./objects.cfg ]";
       };
 
       plugins = mkOption {
         type = types.listOf types.package;
         default = with pkgs; [ monitoring-plugins ssmtp mailutils ];
-        defaultText = "[pkgs.monitoring-plugins pkgs.ssmtp pkgs.mailutils]";
+        defaultText = literalExpression "[pkgs.monitoring-plugins pkgs.ssmtp pkgs.mailutils]";
         description = "
           Packages to be added to the Nagios <envar>PATH</envar>.
           Typically used to add plugins, but can be anything.
@@ -137,7 +137,7 @@ in
       cgiConfigFile = mkOption {
         type = types.package;
         default = nagiosCGICfgFile;
-        defaultText = "nagiosCGICfgFile";
+        defaultText = literalExpression "nagiosCGICfgFile";
         description = "
           Derivation for the configuration file of Nagios CGI scripts
           that can be used in web servers for running the Nagios web interface.
@@ -155,7 +155,7 @@ in
 
       virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-        example = literalExample ''
+        example = literalExpression ''
           { hostName = "example.org";
             adminAddr = "webmaster@example.org";
             enableSSL = true;
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 561ce3eec6255..00bdd9fcda0d3 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -9,9 +9,9 @@ let
     mkdir -p $out/libexec/netdata/plugins.d
     ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin
     ln -s /run/wrappers/bin/cgroup-network $out/libexec/netdata/plugins.d/cgroup-network
-    ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
     ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin
     ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin
+    ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
   '';
 
   plugins = [
@@ -45,7 +45,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.netdata;
-        defaultText = "pkgs.netdata";
+        defaultText = literalExpression "pkgs.netdata";
         description = "Netdata package to use.";
       };
 
@@ -84,8 +84,8 @@ in {
         extraPackages = mkOption {
           type = types.functionTo (types.listOf types.package);
           default = ps: [];
-          defaultText = "ps: []";
-          example = literalExample ''
+          defaultText = literalExpression "ps: []";
+          example = literalExpression ''
             ps: [
               ps.psycopg2
               ps.docker
@@ -102,7 +102,7 @@ in {
       extraPluginPaths = mkOption {
         type = types.listOf types.path;
         default = [ ];
-        example = literalExample ''
+        example = literalExpression ''
           [ "/path/to/plugins.d" ]
         '';
         description = ''
@@ -121,7 +121,7 @@ in {
         type = types.attrsOf types.attrs;
         default = {};
         description = "netdata.conf configuration as nix attributes. cannot be combined with configText.";
-        example = literalExample ''
+        example = literalExpression ''
           global = {
             "debug log" = "syslog";
             "access log" = "syslog";
@@ -211,44 +211,47 @@ in {
 
     systemd.enableCgroupAccounting = true;
 
-    security.wrappers."apps.plugin" = {
-      source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org";
-      capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
-      owner = cfg.user;
-      group = cfg.group;
-      permissions = "u+rx,g+x,o-rwx";
-    };
+    security.wrappers = {
+      "apps.plugin" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org";
+        capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
 
-    security.wrappers."cgroup-network" = {
-      source = "${cfg.package}/libexec/netdata/plugins.d/cgroup-network.org";
-      capabilities = "cap_setuid+ep";
-      owner = cfg.user;
-      group = cfg.group;
-      permissions = "u+rx,g+x,o-rwx";
-    };
+      "cgroup-network" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/cgroup-network.org";
+        capabilities = "cap_setuid+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
 
-    security.wrappers."freeipmi.plugin" = {
-      source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org";
-      capabilities = "cap_dac_override,cap_fowner+ep";
-      owner = cfg.user;
-      group = cfg.group;
-      permissions = "u+rx,g+x,o-rwx";
-    };
+      "perf.plugin" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org";
+        capabilities = "cap_sys_admin+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
 
-    security.wrappers."perf.plugin" = {
-      source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org";
-      capabilities = "cap_sys_admin+ep";
-      owner = cfg.user;
-      group = cfg.group;
-      permissions = "u+rx,g+x,o-rwx";
-    };
+      "slabinfo.plugin" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
+        capabilities = "cap_dac_override+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
 
-    security.wrappers."slabinfo.plugin" = {
-      source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
-      capabilities = "cap_dac_override+ep";
-      owner = cfg.user;
-      group = cfg.group;
-      permissions = "u+rx,g+x,o-rwx";
+    } // optionalAttrs (cfg.package.withIpmi) {
+      "freeipmi.plugin" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org";
+        capabilities = "cap_dac_override,cap_fowner+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
     };
 
     security.pam.loginLimits = [
@@ -258,6 +261,7 @@ in {
 
     users.users = optionalAttrs (cfg.user == defaultUser) {
       ${defaultUser} = {
+        group = defaultUser;
         isSystemUser = true;
       };
     };
diff --git a/nixos/modules/services/monitoring/parsedmarc.md b/nixos/modules/services/monitoring/parsedmarc.md
new file mode 100644
index 0000000000000..d93134a4cc767
--- /dev/null
+++ b/nixos/modules/services/monitoring/parsedmarc.md
@@ -0,0 +1,113 @@
+# parsedmarc {#module-services-parsedmarc}
+[parsedmarc](https://domainaware.github.io/parsedmarc/) is a service
+which parses incoming [DMARC](https://dmarc.org/) reports and stores
+or sends them to a downstream service for further analysis. In
+combination with Elasticsearch, Grafana and the included Grafana
+dashboard, it provides a handy overview of DMARC reports over time.
+
+## Basic usage {#module-services-parsedmarc-basic-usage}
+A very minimal setup which reads incoming reports from an external
+email address and saves them to a local Elasticsearch instance looks
+like this:
+
+```nix
+services.parsedmarc = {
+  enable = true;
+  settings.imap = {
+    host = "imap.example.com";
+    user = "alice@example.com";
+    password = "/path/to/imap_password_file";
+    watch = true;
+  };
+  provision.geoIp = false; # Not recommended!
+};
+```
+
+Note that GeoIP provisioning is disabled in the example for
+simplicity, but should be turned on for fully functional reports.
+
+## Local mail
+Instead of watching an external inbox, a local inbox can be
+automatically provisioned. The recipient's name is by default set to
+`dmarc`, but can be configured in
+[services.parsedmarc.provision.localMail.recipientName](options.html#opt-services.parsedmarc.provision.localMail.recipientName). You
+need to add an MX record pointing to the host. More concretely: for
+the example to work, an MX record needs to be set up for
+`monitoring.example.com` and the complete email address that should be
+configured in the domain's dmarc policy is
+`dmarc@monitoring.example.com`.
+
+```nix
+services.parsedmarc = {
+  enable = true;
+  provision = {
+    localMail = {
+      enable = true;
+      hostname = monitoring.example.com;
+    };
+    geoIp = false; # Not recommended!
+  };
+};
+```
+
+## Grafana and GeoIP
+The reports can be visualized and summarized with parsedmarc's
+official Grafana dashboard. For all views to work, and for the data to
+be complete, GeoIP databases are also required. The following example
+shows a basic deployment where the provisioned Elasticsearch instance
+is automatically added as a Grafana datasource, and the dashboard is
+added to Grafana as well.
+
+```nix
+services.parsedmarc = {
+  enable = true;
+  provision = {
+    localMail = {
+      enable = true;
+      hostname = url;
+    };
+    grafana = {
+      datasource = true;
+      dashboard = true;
+    };
+  };
+};
+
+# Not required, but recommended for full functionality
+services.geoipupdate = {
+  settings = {
+    AccountID = 000000;
+    LicenseKey = "/path/to/license_key_file";
+  };
+};
+
+services.grafana = {
+  enable = true;
+  addr = "0.0.0.0";
+  domain = url;
+  rootUrl = "https://" + url;
+  protocol = "socket";
+  security = {
+    adminUser = "admin";
+    adminPasswordFile = "/path/to/admin_password_file";
+    secretKeyFile = "/path/to/secret_key_file";
+  };
+};
+
+services.nginx = {
+  enable = true;
+  recommendedTlsSettings = true;
+  recommendedOptimisation = true;
+  recommendedGzipSettings = true;
+  recommendedProxySettings = true;
+  upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {};
+  virtualHosts.${url} = {
+    root = config.services.grafana.staticRootPath;
+    enableACME = true;
+    forceSSL = true;
+    locations."/".tryFiles = "$uri @grafana";
+    locations."@grafana".proxyPass = "http://grafana";
+  };
+};
+users.users.nginx.extraGroups = [ "grafana" ];
+```
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
new file mode 100644
index 0000000000000..eeee04b4400ca
--- /dev/null
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -0,0 +1,537 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.parsedmarc;
+  ini = pkgs.formats.ini {};
+in
+{
+  options.services.parsedmarc = {
+
+    enable = lib.mkEnableOption ''
+      parsedmarc, a DMARC report monitoring service
+    '';
+
+    provision = {
+      localMail = {
+        enable = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = ''
+            Whether Postfix and Dovecot should be set up to receive
+            mail locally. parsedmarc will be configured to watch the
+            local inbox as the automatically created user specified in
+            <xref linkend="opt-services.parsedmarc.provision.localMail.recipientName" />
+          '';
+        };
+
+        recipientName = lib.mkOption {
+          type = lib.types.str;
+          default = "dmarc";
+          description = ''
+            The DMARC mail recipient name, i.e. the name part of the
+            email address which receives DMARC reports.
+
+            A local user with this name will be set up and assigned a
+            randomized password on service start.
+          '';
+        };
+
+        hostname = lib.mkOption {
+          type = lib.types.str;
+          default = config.networking.fqdn;
+          defaultText = lib.literalExpression "config.networking.fqdn";
+          example = "monitoring.example.com";
+          description = ''
+            The hostname to use when configuring Postfix.
+
+            Should correspond to the host's fully qualified domain
+            name and the domain part of the email address which
+            receives DMARC reports. You also have to set up an MX record
+            pointing to this domain name.
+          '';
+        };
+      };
+
+      geoIp = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Whether to enable and configure the <link
+          linkend="opt-services.geoipupdate.enable">geoipupdate</link>
+          service to automatically fetch GeoIP databases. Not crucial,
+          but recommended for full functionality.
+
+          To finish the setup, you need to manually set the <xref
+          linkend="opt-services.geoipupdate.settings.AccountID" /> and
+          <xref linkend="opt-services.geoipupdate.settings.LicenseKey" />
+          options.
+        '';
+      };
+
+      elasticsearch = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Whether to set up and use a local instance of Elasticsearch.
+        '';
+      };
+
+      grafana = {
+        datasource = lib.mkOption {
+          type = lib.types.bool;
+          default = cfg.provision.elasticsearch && config.services.grafana.enable;
+          apply = x: x && cfg.provision.elasticsearch;
+          description = ''
+            Whether the automatically provisioned Elasticsearch
+            instance should be added as a grafana datasource. Has no
+            effect unless
+            <xref linkend="opt-services.parsedmarc.provision.elasticsearch" />
+            is also enabled.
+          '';
+        };
+
+        dashboard = lib.mkOption {
+          type = lib.types.bool;
+          default = config.services.grafana.enable;
+          description = ''
+            Whether the official parsedmarc grafana dashboard should
+            be provisioned to the local grafana instance.
+          '';
+        };
+      };
+    };
+
+    settings = lib.mkOption {
+      description = ''
+        Configuration parameters to set in
+        <filename>parsedmarc.ini</filename>. For a full list of
+        available parameters, see
+        <link xlink:href="https://domainaware.github.io/parsedmarc/#configuration-file" />.
+      '';
+
+      type = lib.types.submodule {
+        freeformType = ini.type;
+
+        options = {
+          general = {
+            save_aggregate = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              description = ''
+                Save aggregate report data to Elasticsearch and/or Splunk.
+              '';
+            };
+
+            save_forensic = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              description = ''
+                Save forensic report data to Elasticsearch and/or Splunk.
+              '';
+            };
+          };
+
+          imap = {
+            host = lib.mkOption {
+              type = lib.types.str;
+              default = "localhost";
+              description = ''
+                The IMAP server hostname or IP address.
+              '';
+            };
+
+            port = lib.mkOption {
+              type = lib.types.port;
+              default = 993;
+              description = ''
+                The IMAP server port.
+              '';
+            };
+
+            ssl = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              description = ''
+                Use an encrypted SSL/TLS connection.
+              '';
+            };
+
+            user = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                The IMAP server username.
+              '';
+            };
+
+            password = lib.mkOption {
+              type = with lib.types; nullOr path;
+              default = null;
+              description = ''
+                The path to a file containing the IMAP server password.
+              '';
+            };
+
+            watch = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              description = ''
+                Use the IMAP IDLE command to process messages as they arrive.
+              '';
+            };
+
+            delete = lib.mkOption {
+              type = lib.types.bool;
+              default = false;
+              description = ''
+                Delete messages after processing them, instead of archiving them.
+              '';
+            };
+          };
+
+          smtp = {
+            host = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                The SMTP server hostname or IP address.
+              '';
+            };
+
+            port = lib.mkOption {
+              type = with lib.types; nullOr port;
+              default = null;
+              description = ''
+                The SMTP server port.
+              '';
+            };
+
+            ssl = lib.mkOption {
+              type = with lib.types; nullOr bool;
+              default = null;
+              description = ''
+                Use an encrypted SSL/TLS connection.
+              '';
+            };
+
+            user = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                The SMTP server username.
+              '';
+            };
+
+            password = lib.mkOption {
+              type = with lib.types; nullOr path;
+              default = null;
+              description = ''
+                The path to a file containing the SMTP server password.
+              '';
+            };
+
+            from = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                The <literal>From</literal> address to use for the
+                outgoing mail.
+              '';
+            };
+
+            to = lib.mkOption {
+              type = with lib.types; nullOr (listOf str);
+              default = null;
+              description = ''
+                The addresses to send outgoing mail to.
+              '';
+            };
+          };
+
+          elasticsearch = {
+            hosts = lib.mkOption {
+              default = [];
+              type = with lib.types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              description = ''
+                A list of Elasticsearch hosts to push parsed reports
+                to.
+              '';
+            };
+
+            user = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                Username to use when connecting to Elasticsearch, if
+                required.
+              '';
+            };
+
+            password = lib.mkOption {
+              type = with lib.types; nullOr path;
+              default = null;
+              description = ''
+                The path to a file containing the password to use when
+                connecting to Elasticsearch, if required.
+              '';
+            };
+
+            ssl = lib.mkOption {
+              type = lib.types.bool;
+              default = false;
+              description = ''
+                Whether to use an encrypted SSL/TLS connection.
+              '';
+            };
+
+            cert_path = lib.mkOption {
+              type = lib.types.path;
+              default = "/etc/ssl/certs/ca-certificates.crt";
+              description = ''
+                The path to a TLS certificate bundle used to verify
+                the server's certificate.
+              '';
+            };
+          };
+
+          kafka = {
+            hosts = lib.mkOption {
+              default = [];
+              type = with lib.types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              description = ''
+                A list of Apache Kafka hosts to publish parsed reports
+                to.
+              '';
+            };
+
+            user = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                Username to use when connecting to Kafka, if
+                required.
+              '';
+            };
+
+            password = lib.mkOption {
+              type = with lib.types; nullOr path;
+              default = null;
+              description = ''
+                The path to a file containing the password to use when
+                connecting to Kafka, if required.
+              '';
+            };
+
+            ssl = lib.mkOption {
+              type = with lib.types; nullOr bool;
+              default = null;
+              description = ''
+                Whether to use an encrypted SSL/TLS connection.
+              '';
+            };
+
+            aggregate_topic = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              example = "aggregate";
+              description = ''
+                The Kafka topic to publish aggregate reports on.
+              '';
+            };
+
+            forensic_topic = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              example = "forensic";
+              description = ''
+                The Kafka topic to publish forensic reports on.
+              '';
+            };
+          };
+
+        };
+
+      };
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    services.elasticsearch.enable = lib.mkDefault cfg.provision.elasticsearch;
+
+    services.geoipupdate = lib.mkIf cfg.provision.geoIp {
+      enable = true;
+      settings = {
+        EditionIDs = [
+          "GeoLite2-ASN"
+          "GeoLite2-City"
+          "GeoLite2-Country"
+        ];
+        DatabaseDirectory = "/var/lib/GeoIP";
+      };
+    };
+
+    services.dovecot2 = lib.mkIf cfg.provision.localMail.enable {
+      enable = true;
+      protocols = [ "imap" ];
+    };
+
+    services.postfix = lib.mkIf cfg.provision.localMail.enable {
+      enable = true;
+      origin = cfg.provision.localMail.hostname;
+      config = {
+        myhostname = cfg.provision.localMail.hostname;
+        mydestination = cfg.provision.localMail.hostname;
+      };
+    };
+
+    services.grafana = {
+      declarativePlugins = with pkgs.grafanaPlugins;
+        lib.mkIf cfg.provision.grafana.dashboard [
+          grafana-worldmap-panel
+          grafana-piechart-panel
+        ];
+
+      provision = {
+        enable = cfg.provision.grafana.datasource || cfg.provision.grafana.dashboard;
+        datasources =
+          let
+            pkgVer = lib.getVersion config.services.elasticsearch.package;
+            esVersion =
+              if lib.versionOlder pkgVer "7" then
+                "60"
+              else if lib.versionOlder pkgVer "8" then
+                "70"
+              else
+                throw "When provisioning parsedmarc grafana datasources: unknown Elasticsearch version.";
+          in
+            lib.mkIf cfg.provision.grafana.datasource [
+              {
+                name = "dmarc-ag";
+                type = "elasticsearch";
+                access = "proxy";
+                url = "localhost:9200";
+                jsonData = {
+                  timeField = "date_range";
+                  inherit esVersion;
+                };
+              }
+              {
+                name = "dmarc-fo";
+                type = "elasticsearch";
+                access = "proxy";
+                url = "localhost:9200";
+                jsonData = {
+                  timeField = "date_range";
+                  inherit esVersion;
+                };
+              }
+            ];
+        dashboards = lib.mkIf cfg.provision.grafana.dashboard [{
+          name = "parsedmarc";
+          options.path = "${pkgs.python3Packages.parsedmarc.dashboard}";
+        }];
+      };
+    };
+
+    services.parsedmarc.settings = lib.mkMerge [
+      (lib.mkIf cfg.provision.elasticsearch {
+        elasticsearch = {
+          hosts = [ "localhost:9200" ];
+          ssl = false;
+        };
+      })
+      (lib.mkIf cfg.provision.localMail.enable {
+        imap = {
+          host = "localhost";
+          port = 143;
+          ssl = false;
+          user = cfg.provision.localMail.recipientName;
+          password = "${pkgs.writeText "imap-password" "@imap-password@"}";
+          watch = true;
+        };
+      })
+    ];
+
+    systemd.services.parsedmarc =
+      let
+        # Remove any empty attributes from the config, i.e. empty
+        # lists, empty attrsets and null. This makes it possible to
+        # list interesting options in `settings` without them always
+        # ending up in the resulting config.
+        filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! builtins.elem v [ null [] {} ])) cfg.settings;
+        parsedmarcConfig = ini.generate "parsedmarc.ini" filteredConfig;
+        mkSecretReplacement = file:
+          lib.optionalString (file != null) ''
+            replace-secret '${file}' '${file}' /run/parsedmarc/parsedmarc.ini
+          '';
+      in
+        {
+          wantedBy = [ "multi-user.target" ];
+          after = [ "postfix.service" "dovecot2.service" "elasticsearch.service" ];
+          path = with pkgs; [ replace-secret openssl shadow ];
+          serviceConfig = {
+            ExecStartPre = let
+              startPreFullPrivileges = ''
+                set -o errexit -o pipefail -o nounset -o errtrace
+                shopt -s inherit_errexit
+
+                umask u=rwx,g=,o=
+                cp ${parsedmarcConfig} /run/parsedmarc/parsedmarc.ini
+                chown parsedmarc:parsedmarc /run/parsedmarc/parsedmarc.ini
+                ${mkSecretReplacement cfg.settings.smtp.password}
+                ${mkSecretReplacement cfg.settings.imap.password}
+                ${mkSecretReplacement cfg.settings.elasticsearch.password}
+                ${mkSecretReplacement cfg.settings.kafka.password}
+              '' + lib.optionalString cfg.provision.localMail.enable ''
+                openssl rand -hex 64 >/run/parsedmarc/dmarc_user_passwd
+                replace-secret '@imap-password@' '/run/parsedmarc/dmarc_user_passwd' /run/parsedmarc/parsedmarc.ini
+                echo "Setting new randomized password for user '${cfg.provision.localMail.recipientName}'."
+                cat <(echo -n "${cfg.provision.localMail.recipientName}:") /run/parsedmarc/dmarc_user_passwd | chpasswd
+              '';
+            in
+              "+${pkgs.writeShellScript "parsedmarc-start-pre-full-privileges" startPreFullPrivileges}";
+            Type = "simple";
+            User = "parsedmarc";
+            Group = "parsedmarc";
+            DynamicUser = true;
+            RuntimeDirectory = "parsedmarc";
+            RuntimeDirectoryMode = 0700;
+            CapabilityBoundingSet = "";
+            PrivateDevices = true;
+            PrivateMounts = true;
+            PrivateUsers = true;
+            ProtectClock = true;
+            ProtectControlGroups = true;
+            ProtectHome = true;
+            ProtectHostname = true;
+            ProtectKernelLogs = true;
+            ProtectKernelModules = true;
+            ProtectKernelTunables = true;
+            ProtectProc = "invisible";
+            ProcSubset = "pid";
+            SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+            RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+            RestrictRealtime = true;
+            RestrictNamespaces = true;
+            MemoryDenyWriteExecute = true;
+            LockPersonality = true;
+            SystemCallArchitectures = "native";
+            ExecStart = "${pkgs.python3Packages.parsedmarc}/bin/parsedmarc -c /run/parsedmarc/parsedmarc.ini";
+          };
+        };
+
+    users.users.${cfg.provision.localMail.recipientName} = lib.mkIf cfg.provision.localMail.enable {
+      isNormalUser = true;
+      description = "DMARC mail recipient";
+    };
+  };
+
+  # Don't edit the docbook xml directly, edit the md and generate it:
+  # `pandoc parsedmarc.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > parsedmarc.xml`
+  meta.doc = ./parsedmarc.xml;
+  meta.maintainers = [ lib.maintainers.talyz ];
+}
diff --git a/nixos/modules/services/monitoring/parsedmarc.xml b/nixos/modules/services/monitoring/parsedmarc.xml
new file mode 100644
index 0000000000000..7167b52d0357d
--- /dev/null
+++ b/nixos/modules/services/monitoring/parsedmarc.xml
@@ -0,0 +1,125 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-parsedmarc">
+  <title>parsedmarc</title>
+  <para>
+    <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>
+    is a service which parses incoming
+    <link xlink:href="https://dmarc.org/">DMARC</link> reports and
+    stores or sends them to a downstream service for further analysis.
+    In combination with Elasticsearch, Grafana and the included Grafana
+    dashboard, it provides a handy overview of DMARC reports over time.
+  </para>
+  <section xml:id="module-services-parsedmarc-basic-usage">
+    <title>Basic usage</title>
+    <para>
+      A very minimal setup which reads incoming reports from an external
+      email address and saves them to a local Elasticsearch instance
+      looks like this:
+    </para>
+    <programlisting language="bash">
+services.parsedmarc = {
+  enable = true;
+  settings.imap = {
+    host = &quot;imap.example.com&quot;;
+    user = &quot;alice@example.com&quot;;
+    password = &quot;/path/to/imap_password_file&quot;;
+    watch = true;
+  };
+  provision.geoIp = false; # Not recommended!
+};
+</programlisting>
+    <para>
+      Note that GeoIP provisioning is disabled in the example for
+      simplicity, but should be turned on for fully functional reports.
+    </para>
+  </section>
+  <section xml:id="local-mail">
+    <title>Local mail</title>
+    <para>
+      Instead of watching an external inbox, a local inbox can be
+      automatically provisioned. The recipient’s name is by default set
+      to <literal>dmarc</literal>, but can be configured in
+      <link xlink:href="options.html#opt-services.parsedmarc.provision.localMail.recipientName">services.parsedmarc.provision.localMail.recipientName</link>.
+      You need to add an MX record pointing to the host. More
+      concretely: for the example to work, an MX record needs to be set
+      up for <literal>monitoring.example.com</literal> and the complete
+      email address that should be configured in the domain’s dmarc
+      policy is <literal>dmarc@monitoring.example.com</literal>.
+    </para>
+    <programlisting language="bash">
+services.parsedmarc = {
+  enable = true;
+  provision = {
+    localMail = {
+      enable = true;
+      hostname = monitoring.example.com;
+    };
+    geoIp = false; # Not recommended!
+  };
+};
+</programlisting>
+  </section>
+  <section xml:id="grafana-and-geoip">
+    <title>Grafana and GeoIP</title>
+    <para>
+      The reports can be visualized and summarized with parsedmarc’s
+      official Grafana dashboard. For all views to work, and for the
+      data to be complete, GeoIP databases are also required. The
+      following example shows a basic deployment where the provisioned
+      Elasticsearch instance is automatically added as a Grafana
+      datasource, and the dashboard is added to Grafana as well.
+    </para>
+    <programlisting language="bash">
+services.parsedmarc = {
+  enable = true;
+  provision = {
+    localMail = {
+      enable = true;
+      hostname = url;
+    };
+    grafana = {
+      datasource = true;
+      dashboard = true;
+    };
+  };
+};
+
+# Not required, but recommended for full functionality
+services.geoipupdate = {
+  settings = {
+    AccountID = 000000;
+    LicenseKey = &quot;/path/to/license_key_file&quot;;
+  };
+};
+
+services.grafana = {
+  enable = true;
+  addr = &quot;0.0.0.0&quot;;
+  domain = url;
+  rootUrl = &quot;https://&quot; + url;
+  protocol = &quot;socket&quot;;
+  security = {
+    adminUser = &quot;admin&quot;;
+    adminPasswordFile = &quot;/path/to/admin_password_file&quot;;
+    secretKeyFile = &quot;/path/to/secret_key_file&quot;;
+  };
+};
+
+services.nginx = {
+  enable = true;
+  recommendedTlsSettings = true;
+  recommendedOptimisation = true;
+  recommendedGzipSettings = true;
+  recommendedProxySettings = true;
+  upstreams.grafana.servers.&quot;unix:/${config.services.grafana.socket}&quot; = {};
+  virtualHosts.${url} = {
+    root = config.services.grafana.staticRootPath;
+    enableACME = true;
+    forceSSL = true;
+    locations.&quot;/&quot;.tryFiles = &quot;$uri @grafana&quot;;
+    locations.&quot;@grafana&quot;.proxyPass = &quot;http://grafana&quot;;
+  };
+};
+users.users.nginx.extraGroups = [ &quot;grafana&quot; ];
+</programlisting>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 1b02ebf37045f..1f396634ae015 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -45,7 +45,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.prometheus-alertmanager;
-        defaultText = "pkgs.alertmanager";
+        defaultText = literalExpression "pkgs.alertmanager";
         description = ''
           Package that should be used for alertmanager.
         '';
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 1161d18ab14b2..f20b8dde1abd0 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -7,19 +7,36 @@ let
 
   workingDir = "/var/lib/" + cfg.stateDir;
 
+  prometheusYmlOut = "${workingDir}/prometheus-substituted.yaml";
+
+  triggerReload = pkgs.writeShellScriptBin "trigger-reload-prometheus" ''
+    PATH="${makeBinPath (with pkgs; [ systemd ])}"
+    if systemctl -q is-active prometheus.service; then
+      systemctl reload prometheus.service
+    fi
+  '';
+
+  reload = pkgs.writeShellScriptBin "reload-prometheus" ''
+    PATH="${makeBinPath (with pkgs; [ systemd coreutils gnugrep ])}"
+    cursor=$(journalctl --show-cursor -n0 | grep -oP "cursor: \K.*")
+    kill -HUP $MAINPID
+    journalctl -u prometheus.service --after-cursor="$cursor" -f \
+      | grep -m 1 "Completed loading of configuration file" > /dev/null
+  '';
+
   # a wrapper that verifies that the configuration is valid
   promtoolCheck = what: name: file:
     if cfg.checkConfig then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
         { buildInputs = [ cfg.package ]; } ''
-      ln -s ${file} $out
-      promtool ${what} $out
-    '' else file;
+        ln -s ${file} $out
+        promtool ${what} $out
+      '' else file;
 
   # Pretty-print JSON to a file
   writePrettyJSON = name: x:
-    pkgs.runCommandLocal name {} ''
+    pkgs.runCommandLocal name { } ''
       echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out
     '';
 
@@ -39,48 +56,111 @@ let
     };
   };
 
-  prometheusYml = let
-    yml = if cfg.configText != null then
-      pkgs.writeText "prometheus.yml" cfg.configText
-      else generatedPrometheusYml;
-    in promtoolCheck "check config" "prometheus.yml" yml;
+  prometheusYml =
+    let
+      yml =
+        if cfg.configText != null then
+          pkgs.writeText "prometheus.yml" cfg.configText
+        else generatedPrometheusYml;
+    in
+    promtoolCheck "check config" "prometheus.yml" yml;
 
   cmdlineArgs = cfg.extraFlags ++ [
     "--storage.tsdb.path=${workingDir}/data/"
-    "--config.file=/run/prometheus/prometheus-substituted.yaml"
+    "--config.file=${
+      if cfg.enableReload
+      then "/etc/prometheus/prometheus.yaml"
+      else prometheusYml
+    }"
     "--web.listen-address=${cfg.listenAddress}:${builtins.toString cfg.port}"
     "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
     "--alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
   ] ++ optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}"
-    ++ optional (cfg.retentionTime != null)  "--storage.tsdb.retention.time=${cfg.retentionTime}";
+    ++ optional (cfg.retentionTime != null) "--storage.tsdb.retention.time=${cfg.retentionTime}";
 
   filterValidPrometheus = filterAttrsListRecursive (n: v: !(n == "_module" || v == null));
   filterAttrsListRecursive = pred: x:
     if isAttrs x then
-      listToAttrs (
-        concatMap (name:
-          let v = x.${name}; in
-          if pred name v then [
-            (nameValuePair name (filterAttrsListRecursive pred v))
-          ] else []
-        ) (attrNames x)
-      )
+      listToAttrs
+        (
+          concatMap
+            (name:
+              let v = x.${name}; in
+              if pred name v then [
+                (nameValuePair name (filterAttrsListRecursive pred v))
+              ] else [ ]
+            )
+            (attrNames x)
+        )
     else if isList x then
       map (filterAttrsListRecursive pred) x
     else x;
 
-  mkDefOpt = type : defaultStr : description : mkOpt type (description + ''
+  #
+  # Config types: helper functions
+  #
+
+  mkDefOpt = type: defaultStr: description: mkOpt type (description + ''
 
     Defaults to <literal>${defaultStr}</literal> in prometheus
     when set to <literal>null</literal>.
   '');
 
-  mkOpt = type : description : mkOption {
+  mkOpt = type: description: mkOption {
     type = types.nullOr type;
     default = null;
     inherit description;
   };
 
+  mkSdConfigModule = extraOptions: types.submodule {
+    options = {
+      basic_auth = mkOpt promTypes.basic_auth ''
+        Optional HTTP basic authentication information.
+      '';
+
+      authorization = mkOpt
+        (types.submodule {
+          options = {
+            type = mkDefOpt types.str "Bearer" ''
+              Sets the authentication type.
+            '';
+
+            credentials = mkOpt types.str ''
+              Sets the credentials. It is mutually exclusive with `credentials_file`.
+            '';
+
+            credentials_file = mkOpt types.str ''
+              Sets the credentials to the credentials read from the configured file.
+              It is mutually exclusive with `credentials`.
+            '';
+          };
+        }) ''
+        Optional `Authorization` header configuration.
+      '';
+
+      oauth2 = mkOpt promtypes.oauth2 ''
+        Optional OAuth 2.0 configuration.
+        Cannot be used at the same time as basic_auth or authorization.
+      '';
+
+      proxy_url = mkOpt types.str ''
+        Optional proxy URL.
+      '';
+
+      follow_redirects = mkDefOpt types.bool "true" ''
+        Configure whether HTTP requests follow HTTP 3xx redirects.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        TLS configuration.
+      '';
+    } // extraOptions;
+  };
+
+  #
+  # Config types: general
+  #
+
   promTypes.globalConfig = types.submodule {
     options = {
       scrape_interval = mkDefOpt types.str "1m" ''
@@ -103,153 +183,68 @@ let
     };
   };
 
-  promTypes.remote_read = types.submodule {
+  promTypes.basic_auth = types.submodule {
     options = {
-      url = mkOption {
+      username = mkOption {
         type = types.str;
         description = ''
-          ServerName extension to indicate the name of the server.
-          http://tools.ietf.org/html/rfc4366#section-3.1
+          HTTP username
         '';
       };
-      name = mkOpt types.str ''
-        Name of the remote read config, which if specified must be unique among remote read configs.
-        The name will be used in metrics and logging in place of a generated value to help users distinguish between
-        remote read configs.
-      '';
-      required_matchers = mkOpt (types.attrsOf types.str) ''
-        An optional list of equality matchers which have to be
-        present in a selector to query the remote read endpoint.
-      '';
-      remote_timeout = mkOpt types.str ''
-        Timeout for requests to the remote read endpoint.
-      '';
-      read_recent = mkOpt types.bool ''
-        Whether reads should be made for queries for time ranges that
-        the local storage should have complete data for.
+      password = mkOpt types.str "HTTP password";
+      password_file = mkOpt types.str "HTTP password file";
+    };
+  };
+
+  promTypes.tls_config = types.submodule {
+    options = {
+      ca_file = mkOpt types.str ''
+        CA certificate to validate API server certificate with.
       '';
-      basic_auth = mkOpt (types.submodule {
-        options = {
-          username = mkOption {
-            type = types.str;
-            description = ''
-              HTTP username
-            '';
-          };
-          password = mkOpt types.str "HTTP password";
-          password_file = mkOpt types.str "HTTP password file";
-        };
-      }) ''
-        Sets the `Authorization` header on every remote read request with the
-        configured username and password.
-        password and password_file are mutually exclusive.
+
+      cert_file = mkOpt types.str ''
+        Certificate file for client cert authentication to the server.
       '';
-      bearer_token = mkOpt types.str ''
-        Sets the `Authorization` header on every remote read request with
-        the configured bearer token. It is mutually exclusive with `bearer_token_file`.
+
+      key_file = mkOpt types.str ''
+        Key file for client cert authentication to the server.
       '';
-      bearer_token_file = mkOpt types.str ''
-        Sets the `Authorization` header on every remote read request with the bearer token
-        read from the configured file. It is mutually exclusive with `bearer_token`.
+
+      server_name = mkOpt types.str ''
+        ServerName extension to indicate the name of the server.
+        http://tools.ietf.org/html/rfc4366#section-3.1
       '';
-      tls_config = mkOpt promTypes.tls_config ''
-        Configures the remote read request's TLS settings.
+
+      insecure_skip_verify = mkOpt types.bool ''
+        Disable validation of the server certificate.
       '';
-      proxy_url = mkOpt types.str "Optional Proxy URL.";
     };
   };
 
-  promTypes.remote_write = types.submodule {
+  promtypes.oauth2 = types.submodule {
     options = {
-      url = mkOption {
-        type = types.str;
-        description = ''
-          ServerName extension to indicate the name of the server.
-          http://tools.ietf.org/html/rfc4366#section-3.1
-        '';
-      };
-      remote_timeout = mkOpt types.str ''
-        Timeout for requests to the remote write endpoint.
-      '';
-      write_relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
-        List of remote write relabel configurations.
-      '';
-      name = mkOpt types.str ''
-        Name of the remote write config, which if specified must be unique among remote write configs.
-        The name will be used in metrics and logging in place of a generated value to help users distinguish between
-        remote write configs.
-      '';
-      basic_auth = mkOpt (types.submodule {
-        options = {
-          username = mkOption {
-            type = types.str;
-            description = ''
-              HTTP username
-            '';
-          };
-          password = mkOpt types.str "HTTP password";
-          password_file = mkOpt types.str "HTTP password file";
-        };
-      }) ''
-        Sets the `Authorization` header on every remote write request with the
-        configured username and password.
-        password and password_file are mutually exclusive.
+      client_id = mkOpt types.str ''
+        OAuth client ID.
       '';
-      bearer_token = mkOpt types.str ''
-        Sets the `Authorization` header on every remote write request with
-        the configured bearer token. It is mutually exclusive with `bearer_token_file`.
+
+      client_secret = mkOpt types.str ''
+        OAuth client secret.
       '';
-      bearer_token_file = mkOpt types.str ''
-        Sets the `Authorization` header on every remote write request with the bearer token
-        read from the configured file. It is mutually exclusive with `bearer_token`.
+
+      client_secret_file = mkOpt types.str ''
+        Read the client secret from a file. It is mutually exclusive with `client_secret`.
       '';
-      tls_config = mkOpt promTypes.tls_config ''
-        Configures the remote write request's TLS settings.
+
+      scopes = mkOpt (types.listOf types.str) ''
+        Scopes for the token request.
       '';
-      proxy_url = mkOpt types.str "Optional Proxy URL.";
-      queue_config = mkOpt (types.submodule {
-        options = {
-          capacity = mkOpt types.int ''
-            Number of samples to buffer per shard before we block reading of more
-            samples from the WAL. It is recommended to have enough capacity in each
-            shard to buffer several requests to keep throughput up while processing
-            occasional slow remote requests.
-          '';
-          max_shards = mkOpt types.int ''
-            Maximum number of shards, i.e. amount of concurrency.
-          '';
-          min_shards = mkOpt types.int ''
-            Minimum number of shards, i.e. amount of concurrency.
-          '';
-          max_samples_per_send = mkOpt types.int ''
-            Maximum number of samples per send.
-          '';
-          batch_send_deadline = mkOpt types.str ''
-            Maximum time a sample will wait in buffer.
-          '';
-          min_backoff = mkOpt types.str ''
-            Initial retry delay. Gets doubled for every retry.
-          '';
-          max_backoff = mkOpt types.str ''
-            Maximum retry delay.
-          '';
-        };
-      }) ''
-        Configures the queue used to write to remote storage.
+
+      token_url = mkOpt types.str ''
+        The URL to fetch the token from.
       '';
-      metadata_config = mkOpt (types.submodule {
-        options = {
-          send = mkOpt types.bool ''
-            Whether metric metadata is sent to remote storage or not.
-          '';
-          send_interval = mkOpt types.str ''
-            How frequently metric metadata is sent to remote storage.
-          '';
-        };
-      }) ''
-        Configures the sending of series metadata to remote storage.
-        Metadata configuration is subject to change at any point
-        or be removed in future releases.
+
+      endpoint_params = mkOpt (types.attrsOf types.str) ''
+        Optional parameters to append to the token URL.
       '';
     };
   };
@@ -307,7 +302,7 @@ let
         by the target will be ignored.
       '';
 
-      scheme = mkDefOpt (types.enum ["http" "https"]) "http" ''
+      scheme = mkDefOpt (types.enum [ "http" "https" ]) "http" ''
         The URL scheme with which to fetch metrics from targets.
       '';
 
@@ -315,18 +310,7 @@ let
         Optional HTTP URL parameters.
       '';
 
-      basic_auth = mkOpt (types.submodule {
-        options = {
-          username = mkOption {
-            type = types.str;
-            description = ''
-              HTTP username
-            '';
-          };
-          password = mkOpt types.str "HTTP password";
-          password_file = mkOpt types.str "HTTP password file";
-        };
-      }) ''
+      basic_auth = mkOpt promTypes.basic_auth ''
         Sets the `Authorization` header on every scrape request with the
         configured username and password.
         password and password_file are mutually exclusive.
@@ -352,16 +336,36 @@ let
         Optional proxy URL.
       '';
 
-      ec2_sd_configs = mkOpt (types.listOf promTypes.ec2_sd_config) ''
-        List of EC2 service discovery configurations.
+      azure_sd_configs = mkOpt (types.listOf promTypes.azure_sd_config) ''
+        List of Azure service discovery configurations.
+      '';
+
+      consul_sd_configs = mkOpt (types.listOf promTypes.consul_sd_config) ''
+        List of Consul service discovery configurations.
+      '';
+
+      digitalocean_sd_configs = mkOpt (types.listOf promTypes.digitalocean_sd_config) ''
+        List of DigitalOcean service discovery configurations.
+      '';
+
+      docker_sd_configs = mkOpt (types.listOf promTypes.docker_sd_config) ''
+        List of Docker service discovery configurations.
+      '';
+
+      dockerswarm_sd_configs = mkOpt (types.listOf promTypes.dockerswarm_sd_config) ''
+        List of Docker Swarm service discovery configurations.
       '';
 
       dns_sd_configs = mkOpt (types.listOf promTypes.dns_sd_config) ''
         List of DNS service discovery configurations.
       '';
 
-      consul_sd_configs = mkOpt (types.listOf promTypes.consul_sd_config) ''
-        List of Consul service discovery configurations.
+      ec2_sd_configs = mkOpt (types.listOf promTypes.ec2_sd_config) ''
+        List of EC2 service discovery configurations.
+      '';
+
+      eureka_sd_configs = mkOpt (types.listOf promTypes.eureka_sd_config) ''
+        List of Eureka service discovery configurations.
       '';
 
       file_sd_configs = mkOpt (types.listOf promTypes.file_sd_config) ''
@@ -376,6 +380,62 @@ let
         relevant Prometheus configuration docs</link> for more detail.
       '';
 
+      hetzner_sd_configs = mkOpt (types.listOf promTypes.hetzner_sd_config) ''
+        List of Hetzner service discovery configurations.
+      '';
+
+      http_sd_configs = mkOpt (types.listOf promTypes.http_sd_config) ''
+        List of HTTP service discovery configurations.
+      '';
+
+      kubernetes_sd_configs = mkOpt (types.listOf promTypes.kubernetes_sd_config) ''
+        List of Kubernetes service discovery configurations.
+      '';
+
+      kuma_sd_configs = mkOpt (types.listOf promTypes.kuma_sd_config) ''
+        List of Kuma service discovery configurations.
+      '';
+
+      lightsail_sd_configs = mkOpt (types.listOf promTypes.lightsail_sd_config) ''
+        List of Lightsail service discovery configurations.
+      '';
+
+      linode_sd_configs = mkOpt (types.listOf promTypes.linode_sd_config) ''
+        List of Linode service discovery configurations.
+      '';
+
+      marathon_sd_configs = mkOpt (types.listOf promTypes.marathon_sd_config) ''
+        List of Marathon service discovery configurations.
+      '';
+
+      nerve_sd_configs = mkOpt (types.listOf promTypes.nerve_sd_config) ''
+        List of AirBnB's Nerve service discovery configurations.
+      '';
+
+      openstack_sd_configs = mkOpt (types.listOf promTypes.openstack_sd_config) ''
+        List of OpenStack service discovery configurations.
+      '';
+
+      puppetdb_sd_configs = mkOpt (types.listOf promTypes.puppetdb_sd_config) ''
+        List of PuppetDB service discovery configurations.
+      '';
+
+      scaleway_sd_configs = mkOpt (types.listOf promTypes.scaleway_sd_config) ''
+        List of Scaleway service discovery configurations.
+      '';
+
+      serverset_sd_configs = mkOpt (types.listOf promTypes.serverset_sd_config) ''
+        List of Zookeeper Serverset service discovery configurations.
+      '';
+
+      triton_sd_configs = mkOpt (types.listOf promTypes.triton_sd_config) ''
+        List of Triton Serverset service discovery configurations.
+      '';
+
+      uyuni_sd_configs = mkOpt (types.listOf promTypes.uyuni_sd_config) ''
+        List of Uyuni Serverset service discovery configurations.
+      '';
+
       static_configs = mkOpt (types.listOf promTypes.static_config) ''
         List of labeled target groups for this job.
       '';
@@ -388,29 +448,245 @@ let
         List of metric relabel configurations.
       '';
 
+      body_size_limit = mkDefOpt types.str "0" ''
+        An uncompressed response body larger than this many bytes will cause the
+        scrape to fail. 0 means no limit. Example: 100MB.
+        This is an experimental feature, this behaviour could
+        change or be removed in the future.
+      '';
+
       sample_limit = mkDefOpt types.int "0" ''
         Per-scrape limit on number of scraped samples that will be accepted.
         If more than this number of samples are present after metric relabelling
         the entire scrape will be treated as failed. 0 means no limit.
       '';
+
+      label_limit = mkDefOpt types.int "0" ''
+        Per-scrape limit on number of labels that will be accepted for a sample. If
+        more than this number of labels are present post metric-relabeling, the
+        entire scrape will be treated as failed. 0 means no limit.
+      '';
+
+      label_name_length_limit = mkDefOpt types.int "0" ''
+        Per-scrape limit on length of labels name that will be accepted for a sample.
+        If a label name is longer than this number post metric-relabeling, the entire
+        scrape will be treated as failed. 0 means no limit.
+      '';
+
+      label_value_length_limit = mkDefOpt types.int "0" ''
+        Per-scrape limit on length of labels value that will be accepted for a sample.
+        If a label value is longer than this number post metric-relabeling, the
+        entire scrape will be treated as failed. 0 means no limit.
+      '';
+
+      target_limit = mkDefOpt types.int "0" ''
+        Per-scrape config limit on number of unique targets that will be
+        accepted. If more than this number of targets are present after target
+        relabeling, Prometheus will mark the targets as failed without scraping them.
+        0 means no limit. This is an experimental feature, this behaviour could
+        change in the future.
+      '';
     };
   };
 
-  promTypes.static_config = types.submodule {
+  #
+  # Config types: service discovery
+  #
+
+  # For this one, the docs actually define all types needed to use mkSdConfigModule, but a bunch
+  # of them are marked with 'currently not support by Azure' so we don't bother adding them in
+  # here.
+  promTypes.azure_sd_config = types.submodule {
     options = {
-      targets = mkOption {
-        type = types.listOf types.str;
+      environment = mkDefOpt types.str "AzurePublicCloud" ''
+        The Azure environment.
+      '';
+
+      authentication_method = mkDefOpt (types.enum [ "OAuth" "ManagedIdentity" ]) "OAuth" ''
+        The authentication method, either OAuth or ManagedIdentity.
+        See https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
+      '';
+
+      subscription_id = mkOption {
+        type = types.str;
         description = ''
-          The targets specified by the target group.
+          The subscription ID.
         '';
       };
-      labels = mkOption {
-        type = types.attrsOf types.str;
-        default = {};
+
+      tenant_id = mkOpt types.str ''
+        Optional tenant ID. Only required with authentication_method OAuth.
+      '';
+
+      client_id = mkOpt types.str ''
+        Optional client ID. Only required with authentication_method OAuth.
+      '';
+
+      client_secret = mkOpt types.str ''
+        Optional client secret. Only required with authentication_method OAuth.
+      '';
+
+      refresh_interval = mkDefOpt types.str "300s" ''
+        Refresh interval to re-read the instance list.
+      '';
+
+      port = mkDefOpt types.int "80" ''
+        The port to scrape metrics from. If using the public IP
+        address, this must instead be specified in the relabeling
+        rule.
+      '';
+
+      proxy_url = mkOpt types.str ''
+        Optional proxy URL.
+      '';
+
+      follow_redirects = mkDefOpt types.bool "true" ''
+        Configure whether HTTP requests follow HTTP 3xx redirects.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        TLS configuration.
+      '';
+    };
+  };
+
+  promTypes.consul_sd_config = mkSdConfigModule {
+    server = mkDefOpt types.str "localhost:8500" ''
+      Consul server to query.
+    '';
+
+    token = mkOpt types.str "Consul token";
+
+    datacenter = mkOpt types.str "Consul datacenter";
+
+    scheme = mkDefOpt types.str "http" "Consul scheme";
+
+    username = mkOpt types.str "Consul username";
+
+    password = mkOpt types.str "Consul password";
+
+    tls_config = mkOpt promTypes.tls_config ''
+      Configures the Consul request's TLS settings.
+    '';
+
+    services = mkOpt (types.listOf types.str) ''
+      A list of services for which targets are retrieved.
+    '';
+
+    tags = mkOpt (types.listOf types.str) ''
+      An optional list of tags used to filter nodes for a given
+      service. Services must contain all tags in the list.
+    '';
+
+    node_meta = mkOpt (types.attrsOf types.str) ''
+      Node metadata used to filter nodes for a given service.
+    '';
+
+    tag_separator = mkDefOpt types.str "," ''
+      The string by which Consul tags are joined into the tag label.
+    '';
+
+    allow_stale = mkOpt types.bool ''
+      Allow stale Consul results
+      (see <link xlink:href="https://www.consul.io/api/index.html#consistency-modes"/>).
+
+      Will reduce load on Consul.
+    '';
+
+    refresh_interval = mkDefOpt types.str "30s" ''
+      The time after which the provided names are refreshed.
+
+      On large setup it might be a good idea to increase this value
+      because the catalog will change all the time.
+    '';
+  };
+
+  promTypes.digitalocean_sd_config = mkSdConfigModule {
+    port = mkDefOpt types.int "80" ''
+      The port to scrape metrics from.
+    '';
+
+    refresh_interval = mkDefOpt types.str "60s" ''
+      The time after which the droplets are refreshed.
+    '';
+  };
+
+  mkDockerSdConfigModule = extraOptions: mkSdConfigModule ({
+    host = mkOption {
+      type = types.str;
+      description = ''
+        Address of the Docker daemon.
+      '';
+    };
+
+    port = mkDefOpt types.int "80" ''
+      The port to scrape metrics from, when `role` is nodes, and for discovered
+      tasks and services that don't have published ports.
+    '';
+
+    filters = mkOpt
+      (types.listOf (types.submodule {
+        options = {
+          name = mkOption {
+            type = types.str;
+            description = ''
+              Name of the filter. The available filters are listed in the upstream documentation:
+              Services: <link xlink:href="https://docs.docker.com/engine/api/v1.40/#operation/ServiceList"/>
+              Tasks: <link xlink:href="https://docs.docker.com/engine/api/v1.40/#operation/TaskList"/>
+              Nodes: <link xlink:href="https://docs.docker.com/engine/api/v1.40/#operation/NodeList"/>
+            '';
+          };
+          values = mkOption {
+            type = types.str;
+            description = ''
+              Value for the filter.
+            '';
+          };
+        };
+      })) ''
+      Optional filters to limit the discovery process to a subset of available resources.
+    '';
+
+    refresh_interval = mkDefOpt types.str "60s" ''
+      The time after which the containers are refreshed.
+    '';
+  } // extraOptions);
+
+  promTypes.docker_sd_config = mkDockerSdConfigModule {
+    host_networking_host = mkDefOpt types.str "localhost" ''
+      The host to use if the container is in host networking mode.
+    '';
+  };
+
+  promTypes.dockerswarm_sd_config = mkDockerSdConfigModule {
+    role = mkOption {
+      type = types.enum [ "services" "tasks" "nodes" ];
+      description = ''
+        Role of the targets to retrieve. Must be `services`, `tasks`, or `nodes`.
+      '';
+    };
+  };
+
+  promTypes.dns_sd_config = types.submodule {
+    options = {
+      names = mkOption {
+        type = types.listOf types.str;
         description = ''
-          Labels assigned to all metrics scraped from the targets.
+          A list of DNS SRV record names to be queried.
         '';
       };
+
+      type = mkDefOpt (types.enum [ "SRV" "A" "AAAA" ]) "SRV" ''
+        The type of DNS query to perform. One of SRV, A, or AAAA.
+      '';
+
+      port = mkOpt types.int ''
+        The port number used if the query type is not SRV.
+      '';
+
+      refresh_interval = mkDefOpt types.str "30s" ''
+        The time after which the provided names are refreshed.
+      '';
     };
   };
 
@@ -419,7 +695,7 @@ let
       region = mkOption {
         type = types.str;
         description = ''
-          The AWS Region.
+          The AWS Region. If blank, the region from the instance metadata is used.
         '';
       };
       endpoint = mkOpt types.str ''
@@ -436,7 +712,7 @@ let
          <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
       '';
 
-      profile = mkOpt  types.str ''
+      profile = mkOpt types.str ''
         Named AWS profile used to connect to the API.
       '';
 
@@ -454,163 +730,653 @@ let
         rule.
       '';
 
-      filters = mkOpt (types.listOf promTypes.filter) ''
+      filters = mkOpt
+        (types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = ''
+                See <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html">this list</link>
+                for the available filters.
+              '';
+            };
+
+            values = mkOption {
+              type = types.listOf types.str;
+              default = [ ];
+              description = ''
+                Value of the filter.
+              '';
+            };
+          };
+        })) ''
         Filters can be used optionally to filter the instance list by other criteria.
       '';
     };
   };
 
-  promTypes.filter = types.submodule {
+  promTypes.eureka_sd_config = mkSdConfigModule {
+    server = mkOption {
+      type = types.str;
+      description = ''
+        The URL to connect to the Eureka server.
+      '';
+    };
+  };
+
+  promTypes.file_sd_config = types.submodule {
+    options = {
+      files = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          Patterns for files from which target groups are extracted. Refer
+          to the Prometheus documentation for permitted filename patterns
+          and formats.
+        '';
+      };
+
+      refresh_interval = mkDefOpt types.str "5m" ''
+        Refresh interval to re-read the files.
+      '';
+    };
+  };
+
+  promTypes.gce_sd_config = types.submodule {
     options = {
-      name = mkOption {
+      # Use `mkOption` instead of `mkOpt` for project and zone because they are
+      # required configuration values for `gce_sd_config`.
+      project = mkOption {
         type = types.str;
         description = ''
-          See <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html">this list</link>
-          for the available filters.
+          The GCP Project.
         '';
       };
 
-      values = mkOption {
-        type = types.listOf types.str;
-        default = [];
+      zone = mkOption {
+        type = types.str;
         description = ''
-          Value of the filter.
+          The zone of the scrape targets. If you need multiple zones use multiple
+          gce_sd_configs.
         '';
       };
+
+      filter = mkOpt types.str ''
+        Filter can be used optionally to filter the instance list by other
+        criteria Syntax of this filter string is described here in the filter
+        query parameter section: <link
+        xlink:href="https://cloud.google.com/compute/docs/reference/latest/instances/list"
+        />.
+      '';
+
+      refresh_interval = mkDefOpt types.str "60s" ''
+        Refresh interval to re-read the cloud instance list.
+      '';
+
+      port = mkDefOpt types.port "80" ''
+        The port to scrape metrics from. If using the public IP address, this
+        must instead be specified in the relabeling rule.
+      '';
+
+      tag_separator = mkDefOpt types.str "," ''
+        The tag separator used to separate concatenated GCE instance network tags.
+
+        See the GCP documentation on network tags for more information:
+        <link xlink:href="https://cloud.google.com/vpc/docs/add-remove-network-tags" />
+      '';
     };
   };
 
-  promTypes.dns_sd_config = types.submodule {
+  promTypes.hetzner_sd_config = mkSdConfigModule {
+    role = mkOption {
+      type = types.enum [ "robot" "hcloud" ];
+      description = ''
+        The Hetzner role of entities that should be discovered.
+        One of <literal>robot</literal> or <literal>hcloud</literal>.
+      '';
+    };
+
+    port = mkDefOpt types.int "80" ''
+      The port to scrape metrics from.
+    '';
+
+    refresh_interval = mkDefOpt types.str "60s" ''
+      The time after which the servers are refreshed.
+    '';
+  };
+
+  promTypes.http_sd_config = types.submodule {
     options = {
-      names = mkOption {
-        type = types.listOf types.str;
+      url = mkOption {
+        type = types.str;
         description = ''
-          A list of DNS SRV record names to be queried.
+          URL from which the targets are fetched.
         '';
       };
 
-      refresh_interval = mkDefOpt types.str "30s" ''
-        The time after which the provided names are refreshed.
+      refresh_interval = mkDefOpt types.str "60s" ''
+        Refresh interval to re-query the endpoint.
+      '';
+
+      basic_auth = mkOpt promTypes.basic_auth ''
+        Authentication information used to authenticate to the API server.
+        password and password_file are mutually exclusive.
+      '';
+
+      proxy_url = mkOpt types.str ''
+        Optional proxy URL.
+      '';
+
+      follow_redirects = mkDefOpt types.bool "true" ''
+        Configure whether HTTP requests follow HTTP 3xx redirects.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the scrape request's TLS settings.
       '';
     };
   };
 
-  promTypes.consul_sd_config = types.submodule {
-    options = {
-      server = mkDefOpt types.str "localhost:8500" ''
-        Consul server to query.
+  promTypes.kubernetes_sd_config = mkSdConfigModule {
+    api_server = mkOpt types.str ''
+      The API server addresses. If left empty, Prometheus is assumed to run inside
+      of the cluster and will discover API servers automatically and use the pod's
+      CA certificate and bearer token file at /var/run/secrets/kubernetes.io/serviceaccount/.
+    '';
+
+    role = mkOption {
+      type = types.enum [ "endpoints" "service" "pod" "node" "ingress" ];
+      description = ''
+        The Kubernetes role of entities that should be discovered.
+        One of endpoints, service, pod, node, or ingress.
       '';
+    };
+
+    kubeconfig_file = mkOpt types.str ''
+      Optional path to a kubeconfig file.
+      Note that api_server and kube_config are mutually exclusive.
+    '';
 
-      token = mkOpt types.str "Consul token";
+    namespaces = mkOpt
+      (
+        types.submodule {
+          options = {
+            names = mkOpt (types.listOf types.str) ''
+              Namespace name.
+            '';
+          };
+        }
+      ) ''
+      Optional namespace discovery. If omitted, all namespaces are used.
+    '';
 
-      datacenter = mkOpt types.str "Consul datacenter";
+    selectors = mkOpt
+      (
+        types.listOf (
+          types.submodule {
+            options = {
+              role = mkOption {
+                type = types.str;
+                description = ''
+                  Selector role
+                '';
+              };
+
+              label = mkOpt types.str ''
+                Selector label
+              '';
+
+              field = mkOpt types.str ''
+                Selector field
+              '';
+            };
+          }
+        )
+      ) ''
+      Optional label and field selectors to limit the discovery process to a subset of available resources.
+      See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/
+      and https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ to learn more about the possible
+      filters that can be used. Endpoints role supports pod, service and endpoints selectors, other roles
+      only support selectors matching the role itself (e.g. node role can only contain node selectors).
+
+      Note: When making decision about using field/label selector make sure that this
+      is the best approach - it will prevent Prometheus from reusing single list/watch
+      for all scrape configs. This might result in a bigger load on the Kubernetes API,
+      because per each selector combination there will be additional LIST/WATCH. On the other hand,
+      if you just want to monitor small subset of pods in large cluster it's recommended to use selectors.
+      Decision, if selectors should be used or not depends on the particular situation.
+    '';
+  };
 
-      scheme = mkDefOpt types.str "http" "Consul scheme";
+  promTypes.kuma_sd_config = mkSdConfigModule {
+    server = mkOption {
+      type = types.str;
+      description = ''
+        Address of the Kuma Control Plane's MADS xDS server.
+      '';
+    };
 
-      username = mkOpt types.str "Consul username";
+    refresh_interval = mkDefOpt types.str "30s" ''
+      The time to wait between polling update requests.
+    '';
 
-      password = mkOpt types.str "Consul password";
+    fetch_timeout = mkDefOpt types.str "2m" ''
+      The time after which the monitoring assignments are refreshed.
+    '';
+  };
 
-      tls_config = mkOpt promTypes.tls_config ''
-        Configures the Consul request's TLS settings.
+  promTypes.lightsail_sd_config = types.submodule {
+    options = {
+      region = mkOpt types.str ''
+        The AWS region. If blank, the region from the instance metadata is used.
       '';
 
-      services = mkOpt (types.listOf types.str) ''
-        A list of services for which targets are retrieved.
+      endpoint = mkOpt types.str ''
+        Custom endpoint to be used.
       '';
 
-      tags = mkOpt (types.listOf types.str) ''
-        An optional list of tags used to filter nodes for a given
-        service. Services must contain all tags in the list.
+      access_key = mkOpt types.str ''
+        The AWS API keys. If blank, the environment variable <literal>AWS_ACCESS_KEY_ID</literal> is used.
       '';
 
-      node_meta = mkOpt (types.attrsOf types.str) ''
-        Node metadata used to filter nodes for a given service.
+      secret_key = mkOpt types.str ''
+        The AWS API keys. If blank, the environment variable <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
       '';
 
-      tag_separator = mkDefOpt types.str "," ''
-        The string by which Consul tags are joined into the tag label.
+      profile = mkOpt types.str ''
+        Named AWS profile used to connect to the API.
       '';
 
-      allow_stale = mkOpt types.bool ''
-        Allow stale Consul results
-        (see <link xlink:href="https://www.consul.io/api/index.html#consistency-modes"/>).
+      role_arn = mkOpt types.str ''
+        AWS Role ARN, an alternative to using AWS API keys.
+      '';
 
-        Will reduce load on Consul.
+      refresh_interval = mkDefOpt types.str "60s" ''
+        Refresh interval to re-read the instance list.
       '';
 
-      refresh_interval = mkDefOpt types.str "30s" ''
-        The time after which the provided names are refreshed.
+      port = mkDefOpt types.int "80" ''
+        The port to scrape metrics from. If using the public IP address, this must
+        instead be specified in the relabeling rule.
+      '';
+    };
+  };
+
+  promTypes.linode_sd_config = mkSdConfigModule {
+    port = mkDefOpt types.int "80" ''
+      The port to scrape metrics from.
+    '';
 
-        On large setup it might be a good idea to increase this value
-        because the catalog will change all the time.
+    tag_separator = mkDefOpt types.str "," ''
+      The string by which Linode Instance tags are joined into the tag label.
+    '';
+
+    refresh_interval = mkDefOpt types.str "60s" ''
+      The time after which the linode instances are refreshed.
+    '';
+  };
+
+  promTypes.marathon_sd_config = mkSdConfigModule {
+    servers = mkOption {
+      type = types.listOf types.str;
+      description = ''
+        List of URLs to be used to contact Marathon servers. You need to provide at least one server URL.
       '';
     };
+
+    refresh_interval = mkDefOpt types.str "30s" ''
+      Polling interval.
+    '';
+
+    auth_token = mkOpt types.str ''
+      Optional authentication information for token-based authentication:
+      <link xlink:href="https://docs.mesosphere.com/1.11/security/ent/iam-api/#passing-an-authentication-token" />
+      It is mutually exclusive with <literal>auth_token_file</literal> and other authentication mechanisms.
+    '';
+
+    auth_token_file = mkOpt types.str ''
+      Optional authentication information for token-based authentication:
+      <link xlink:href="https://docs.mesosphere.com/1.11/security/ent/iam-api/#passing-an-authentication-token" />
+      It is mutually exclusive with <literal>auth_token</literal> and other authentication mechanisms.
+    '';
   };
 
-  promTypes.file_sd_config = types.submodule {
+  promTypes.nerve_sd_config = types.submodule {
     options = {
-      files = mkOption {
+      servers = mkOption {
         type = types.listOf types.str;
         description = ''
-          Patterns for files from which target groups are extracted. Refer
-          to the Prometheus documentation for permitted filename patterns
-          and formats.
+          The Zookeeper servers.
         '';
       };
 
-      refresh_interval = mkDefOpt types.str "5m" ''
-        Refresh interval to re-read the files.
+      paths = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          Paths can point to a single service, or the root of a tree of services.
+        '';
+      };
+
+      timeout = mkDefOpt types.str "10s" ''
+        Timeout value.
       '';
     };
   };
 
-  promTypes.gce_sd_config = types.submodule {
+  promTypes.openstack_sd_config = types.submodule {
+    options =
+      let
+        userDescription = ''
+          username is required if using Identity V2 API. Consult with your provider's
+          control panel to discover your account's username. In Identity V3, either
+          userid or a combination of username and domain_id or domain_name are needed.
+        '';
+
+        domainDescription = ''
+          At most one of domain_id and domain_name must be provided if using username
+          with Identity V3. Otherwise, either are optional.
+        '';
+
+        projectDescription = ''
+          The project_id and project_name fields are optional for the Identity V2 API.
+          Some providers allow you to specify a project_name instead of the project_id.
+          Some require both. Your provider's authentication policies will determine
+          how these fields influence authentication.
+        '';
+
+        applicationDescription = ''
+          The application_credential_id or application_credential_name fields are
+          required if using an application credential to authenticate. Some providers
+          allow you to create an application credential to authenticate rather than a
+          password.
+        '';
+      in
+      {
+        role = mkOption {
+          type = types.str;
+          description = ''
+            The OpenStack role of entities that should be discovered.
+          '';
+        };
+
+        region = mkOption {
+          type = types.str;
+          description = ''
+            The OpenStack Region.
+          '';
+        };
+
+        identity_endpoint = mkOpt types.str ''
+          identity_endpoint specifies the HTTP endpoint that is required to work with
+          the Identity API of the appropriate version. While it's ultimately needed by
+          all of the identity services, it will often be populated by a provider-level
+          function.
+        '';
+
+        username = mkOpt types.str userDescription;
+        userid = mkOpt types.str userDescription;
+
+        password = mkOpt types.str ''
+          password for the Identity V2 and V3 APIs. Consult with your provider's
+          control panel to discover your account's preferred method of authentication.
+        '';
+
+        domain_name = mkOpt types.str domainDescription;
+        domain_id = mkOpt types.str domainDescription;
+
+        project_name = mkOpt types.str projectDescription;
+        project_id = mkOpt types.str projectDescription;
+
+        application_credential_name = mkOpt types.str applicationDescription;
+        application_credential_id = mkOpt types.str applicationDescription;
+
+        application_credential_secret = mkOpt types.str ''
+          The application_credential_secret field is required if using an application
+          credential to authenticate.
+        '';
+
+        all_tenants = mkDefOpt types.bool "false" ''
+          Whether the service discovery should list all instances for all projects.
+          It is only relevant for the 'instance' role and usually requires admin permissions.
+        '';
+
+        refresh_interval = mkDefOpt types.str "60s" ''
+          Refresh interval to re-read the instance list.
+        '';
+
+        port = mkDefOpt types.int "80" ''
+          The port to scrape metrics from. If using the public IP address, this must
+          instead be specified in the relabeling rule.
+        '';
+
+        availability = mkDefOpt (types.enum [ "public" "admin" "internal" ]) "public" ''
+          The availability of the endpoint to connect to. Must be one of public, admin or internal.
+        '';
+
+        tls_config = mkOpt promTypes.tls_config ''
+          TLS configuration.
+        '';
+      };
+  };
+
+  promTypes.puppetdb_sd_config = mkSdConfigModule {
+    url = mkOption {
+      type = types.str;
+      description = ''
+        The URL of the PuppetDB root query endpoint.
+      '';
+    };
+
+    query = mkOption {
+      type = types.str;
+      description = ''
+        Puppet Query Language (PQL) query. Only resources are supported.
+        https://puppet.com/docs/puppetdb/latest/api/query/v4/pql.html
+      '';
+    };
+
+    include_parameters = mkDefOpt types.bool "false" ''
+      Whether to include the parameters as meta labels.
+      Due to the differences between parameter types and Prometheus labels,
+      some parameters might not be rendered. The format of the parameters might
+      also change in future releases.
+
+      Note: Enabling this exposes parameters in the Prometheus UI and API. Make sure
+      that you don't have secrets exposed as parameters if you enable this.
+    '';
+
+    refresh_interval = mkDefOpt types.str "60s" ''
+      Refresh interval to re-read the resources list.
+    '';
+
+    port = mkDefOpt types.int "80" ''
+      The port to scrape metrics from.
+    '';
+  };
+
+  promTypes.scaleway_sd_config = types.submodule {
     options = {
-      # Use `mkOption` instead of `mkOpt` for project and zone because they are
-      # required configuration values for `gce_sd_config`.
-      project = mkOption {
+      access_key = mkOption {
         type = types.str;
         description = ''
-          The GCP Project.
+          Access key to use. https://console.scaleway.com/project/credentials
         '';
       };
 
-      zone = mkOption {
+      secret_key = mkOpt types.str ''
+        Secret key to use when listing targets. https://console.scaleway.com/project/credentials
+        It is mutually exclusive with `secret_key_file`.
+      '';
+
+      secret_key_file = mkOpt types.str ''
+        Sets the secret key with the credentials read from the configured file.
+        It is mutually exclusive with `secret_key`.
+      '';
+
+      project_id = mkOption {
         type = types.str;
         description = ''
-          The zone of the scrape targets. If you need multiple zones use multiple
-          gce_sd_configs.
+          Project ID of the targets.
         '';
       };
 
-      filter = mkOpt types.str ''
-        Filter can be used optionally to filter the instance list by other
-        criteria Syntax of this filter string is described here in the filter
-        query parameter section: <link
-        xlink:href="https://cloud.google.com/compute/docs/reference/latest/instances/list"
-        />.
+      role = mkOption {
+        type = types.enum [ "instance" "baremetal" ];
+        description = ''
+          Role of the targets to retrieve. Must be `instance` or `baremetal`.
+        '';
+      };
+
+      port = mkDefOpt types.int "80" ''
+        The port to scrape metrics from.
+      '';
+
+      api_url = mkDefOpt types.str "https://api.scaleway.com" ''
+        API URL to use when doing the server listing requests.
+      '';
+
+      zone = mkDefOpt types.str "fr-par-1" ''
+        Zone is the availability zone of your targets (e.g. fr-par-1).
+      '';
+
+      name_filter = mkOpt types.str ''
+        Specify a name filter (works as a LIKE) to apply on the server listing request.
+      '';
+
+      tags_filter = mkOpt (types.listOf types.str) ''
+        Specify a tag filter (a server needs to have all defined tags to be listed) to apply on the server listing request.
       '';
 
       refresh_interval = mkDefOpt types.str "60s" ''
-        Refresh interval to re-read the cloud instance list.
+        Refresh interval to re-read the managed targets list.
       '';
 
-      port = mkDefOpt types.port "80" ''
-        The port to scrape metrics from. If using the public IP address, this
-        must instead be specified in the relabeling rule.
+      proxy_url = mkOpt types.str ''
+        Optional proxy URL.
       '';
 
-      tag_separator = mkDefOpt types.str "," ''
-        The tag separator used to separate concatenated GCE instance network tags.
+      follow_redirects = mkDefOpt types.bool "true" ''
+        Configure whether HTTP requests follow HTTP 3xx redirects.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        TLS configuration.
+      '';
+    };
+  };
+
+  # These are exactly the same.
+  promTypes.serverset_sd_config = promTypes.nerve_sd_config;
+
+  promTypes.triton_sd_config = types.submodule {
+    options = {
+      account = mkOption {
+        type = types.str;
+        description = ''
+          The account to use for discovering new targets.
+        '';
+      };
+
+      role = mkDefOpt (types.enum [ "container" "cn" ]) "container" ''
+        The type of targets to discover, can be set to:
+        - "container" to discover virtual machines (SmartOS zones, lx/KVM/bhyve branded zones) running on Triton
+        - "cn" to discover compute nodes (servers/global zones) making up the Triton infrastructure
+      '';
+
+      dns_suffix = mkOption {
+        type = types.str;
+        description = ''
+          The DNS suffix which should be applied to target.
+        '';
+      };
+
+      endpoint = mkOption {
+        type = types.str;
+        description = ''
+          The Triton discovery endpoint (e.g. <literal>cmon.us-east-3b.triton.zone</literal>). This is
+          often the same value as dns_suffix.
+        '';
+      };
+
+      groups = mkOpt (types.listOf types.str) ''
+        A list of groups for which targets are retrieved, only supported when targeting the <literal>container</literal> role.
+        If omitted all containers owned by the requesting account are scraped.
+      '';
+
+      port = mkDefOpt types.int "9163" ''
+        The port to use for discovery and metric scraping.
+      '';
 
-        See the GCP documentation on network tags for more information: <link
-        xlink:href="https://cloud.google.com/vpc/docs/add-remove-network-tags"
-        />
+      refresh_interval = mkDefOpt types.str "60s" ''
+        The interval which should be used for refreshing targets.
+      '';
+
+      version = mkDefOpt types.int "1" ''
+        The Triton discovery API version.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        TLS configuration.
       '';
     };
   };
 
+  promTypes.uyuni_sd_config = mkSdConfigModule {
+    server = mkOption {
+      type = types.str;
+      description = ''
+        The URL to connect to the Uyuni server.
+      '';
+    };
+
+    username = mkOption {
+      type = types.str;
+      description = ''
+        Credentials are used to authenticate the requests to Uyuni API.
+      '';
+    };
+
+    password = mkOption {
+      type = types.str;
+      description = ''
+        Credentials are used to authenticate the requests to Uyuni API.
+      '';
+    };
+
+    entitlement = mkDefOpt types.str "monitoring_entitled" ''
+      The entitlement string to filter eligible systems.
+    '';
+
+    separator = mkDefOpt types.str "," ''
+      The string by which Uyuni group names are joined into the groups label
+    '';
+
+    refresh_interval = mkDefOpt types.str "60s" ''
+      Refresh interval to re-read the managed targets list.
+    '';
+  };
+
+  promTypes.static_config = types.submodule {
+    options = {
+      targets = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The targets specified by the target group.
+        '';
+      };
+      labels = mkOption {
+        type = types.attrsOf types.str;
+        default = { };
+        description = ''
+          Labels assigned to all metrics scraped from the targets.
+        '';
+      };
+    };
+  };
+
+  #
+  # Config types: relabling
+  #
+
   promTypes.relabel_config = types.submodule {
     options = {
       source_labels = mkOpt (types.listOf types.str) ''
@@ -642,41 +1408,154 @@ let
       '';
 
       action =
-        mkDefOpt (types.enum ["replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep"]) "replace" ''
-        Action to perform based on regex matching.
-      '';
+        mkDefOpt (types.enum [ "replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
+          Action to perform based on regex matching.
+        '';
     };
   };
 
-  promTypes.tls_config = types.submodule {
+  #
+  # Config types : remote read / write
+  #
+
+  promTypes.remote_write = types.submodule {
     options = {
-      ca_file = mkOpt types.str ''
-        CA certificate to validate API server certificate with.
+      url = mkOption {
+        type = types.str;
+        description = ''
+          ServerName extension to indicate the name of the server.
+          http://tools.ietf.org/html/rfc4366#section-3.1
+        '';
+      };
+      remote_timeout = mkOpt types.str ''
+        Timeout for requests to the remote write endpoint.
       '';
-
-      cert_file = mkOpt types.str ''
-        Certificate file for client cert authentication to the server.
+      write_relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
+        List of remote write relabel configurations.
       '';
-
-      key_file = mkOpt types.str ''
-        Key file for client cert authentication to the server.
+      name = mkOpt types.str ''
+        Name of the remote write config, which if specified must be unique among remote write configs.
+        The name will be used in metrics and logging in place of a generated value to help users distinguish between
+        remote write configs.
       '';
-
-      server_name = mkOpt types.str ''
-        ServerName extension to indicate the name of the server.
-        http://tools.ietf.org/html/rfc4366#section-3.1
+      basic_auth = mkOpt promTypes.basic_auth ''
+        Sets the `Authorization` header on every remote write request with the
+        configured username and password.
+        password and password_file are mutually exclusive.
+      '';
+      bearer_token = mkOpt types.str ''
+        Sets the `Authorization` header on every remote write request with
+        the configured bearer token. It is mutually exclusive with `bearer_token_file`.
+      '';
+      bearer_token_file = mkOpt types.str ''
+        Sets the `Authorization` header on every remote write request with the bearer token
+        read from the configured file. It is mutually exclusive with `bearer_token`.
       '';
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the remote write request's TLS settings.
+      '';
+      proxy_url = mkOpt types.str "Optional Proxy URL.";
+      queue_config = mkOpt
+        (types.submodule {
+          options = {
+            capacity = mkOpt types.int ''
+              Number of samples to buffer per shard before we block reading of more
+              samples from the WAL. It is recommended to have enough capacity in each
+              shard to buffer several requests to keep throughput up while processing
+              occasional slow remote requests.
+            '';
+            max_shards = mkOpt types.int ''
+              Maximum number of shards, i.e. amount of concurrency.
+            '';
+            min_shards = mkOpt types.int ''
+              Minimum number of shards, i.e. amount of concurrency.
+            '';
+            max_samples_per_send = mkOpt types.int ''
+              Maximum number of samples per send.
+            '';
+            batch_send_deadline = mkOpt types.str ''
+              Maximum time a sample will wait in buffer.
+            '';
+            min_backoff = mkOpt types.str ''
+              Initial retry delay. Gets doubled for every retry.
+            '';
+            max_backoff = mkOpt types.str ''
+              Maximum retry delay.
+            '';
+          };
+        }) ''
+        Configures the queue used to write to remote storage.
+      '';
+      metadata_config = mkOpt
+        (types.submodule {
+          options = {
+            send = mkOpt types.bool ''
+              Whether metric metadata is sent to remote storage or not.
+            '';
+            send_interval = mkOpt types.str ''
+              How frequently metric metadata is sent to remote storage.
+            '';
+          };
+        }) ''
+        Configures the sending of series metadata to remote storage.
+        Metadata configuration is subject to change at any point
+        or be removed in future releases.
+      '';
+    };
+  };
 
-      insecure_skip_verify = mkOpt types.bool ''
-        Disable validation of the server certificate.
+  promTypes.remote_read = types.submodule {
+    options = {
+      url = mkOption {
+        type = types.str;
+        description = ''
+          ServerName extension to indicate the name of the server.
+          http://tools.ietf.org/html/rfc4366#section-3.1
+        '';
+      };
+      name = mkOpt types.str ''
+        Name of the remote read config, which if specified must be unique among remote read configs.
+        The name will be used in metrics and logging in place of a generated value to help users distinguish between
+        remote read configs.
+      '';
+      required_matchers = mkOpt (types.attrsOf types.str) ''
+        An optional list of equality matchers which have to be
+        present in a selector to query the remote read endpoint.
+      '';
+      remote_timeout = mkOpt types.str ''
+        Timeout for requests to the remote read endpoint.
+      '';
+      read_recent = mkOpt types.bool ''
+        Whether reads should be made for queries for time ranges that
+        the local storage should have complete data for.
+      '';
+      basic_auth = mkOpt promTypes.basic_auth ''
+        Sets the `Authorization` header on every remote read request with the
+        configured username and password.
+        password and password_file are mutually exclusive.
       '';
+      bearer_token = mkOpt types.str ''
+        Sets the `Authorization` header on every remote read request with
+        the configured bearer token. It is mutually exclusive with `bearer_token_file`.
+      '';
+      bearer_token_file = mkOpt types.str ''
+        Sets the `Authorization` header on every remote read request with the bearer token
+        read from the configured file. It is mutually exclusive with `bearer_token`.
+      '';
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the remote read request's TLS settings.
+      '';
+      proxy_url = mkOpt types.str "Optional Proxy URL.";
     };
   };
 
-in {
+in
+{
 
   imports = [
     (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ])
+    (mkRemovedOptionModule [ "services" "prometheus" "environmentFile" ]
+      "It has been removed since it was causing issues (https://github.com/NixOS/nixpkgs/issues/126083) and Prometheus now has native support for secret files, i.e. `basic_auth.password_file` and `authorization.credentials_file`.")
   ];
 
   options.services.prometheus = {
@@ -692,7 +1571,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.prometheus;
-      defaultText = "pkgs.prometheus";
+      defaultText = literalExpression "pkgs.prometheus";
       description = ''
         The prometheus package that should be used.
       '';
@@ -725,48 +1604,22 @@ in {
 
     extraFlags = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       description = ''
         Extra commandline options when launching Prometheus.
       '';
     };
 
-    environmentFile = mkOption {
-      type = types.nullOr types.path;
-      default = null;
-      example = "/root/prometheus.env";
-      description = ''
-        Environment file as defined in <citerefentry>
-        <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
-        </citerefentry>.
-
-        Secrets may be passed to the service without adding them to the
-        world-readable Nix store, by specifying placeholder variables as
-        the option value in Nix and setting these variables accordingly in the
-        environment file.
-
-        Environment variables from this file will be interpolated into the
-        config file using envsubst with this syntax:
-        <literal>$ENVIRONMENT ''${VARIABLE}</literal>
-
-        <programlisting>
-          # Example scrape config entry handling an OAuth bearer token
-          {
-            job_name = "home_assistant";
-            metrics_path = "/api/prometheus";
-            scheme = "https";
-            bearer_token = "\''${HOME_ASSISTANT_BEARER_TOKEN}";
-            [...]
-          }
-        </programlisting>
-
-        <programlisting>
-          # Content of the environment file
-          HOME_ASSISTANT_BEARER_TOKEN=someoauthbearertoken
-        </programlisting>
+    enableReload = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Reload prometheus when configuration file changes (instead of restart).
 
-        Note that this file needs to be available on the host on which
-        <literal>Prometheus</literal> is running.
+        The following property holds: switching to a configuration
+        (<literal>switch-to-configuration</literal>) that changes the prometheus
+        configuration only finishes successully when prometheus has finished
+        loading the new configuration.
       '';
     };
 
@@ -782,7 +1635,7 @@ in {
 
     globalConfig = mkOption {
       type = promTypes.globalConfig;
-      default = {};
+      default = { };
       description = ''
         Parameters that are valid in all  configuration contexts. They
         also serve as defaults for other configuration sections
@@ -791,7 +1644,7 @@ in {
 
     remoteRead = mkOption {
       type = types.listOf promTypes.remote_read;
-      default = [];
+      default = [ ];
       description = ''
         Parameters of the endpoints to query from.
         See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read">the official documentation</link> for more information.
@@ -800,7 +1653,7 @@ in {
 
     remoteWrite = mkOption {
       type = types.listOf promTypes.remote_write;
-      default = [];
+      default = [ ];
       description = ''
         Parameters of the endpoints to send samples to.
         See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write">the official documentation</link> for more information.
@@ -809,7 +1662,7 @@ in {
 
     rules = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       description = ''
         Alerting and/or Recording rules to evaluate at runtime.
       '';
@@ -817,7 +1670,7 @@ in {
 
     ruleFiles = mkOption {
       type = types.listOf types.path;
-      default = [];
+      default = [ ];
       description = ''
         Any additional rules files to include in this configuration.
       '';
@@ -825,7 +1678,7 @@ in {
 
     scrapeConfigs = mkOption {
       type = types.listOf promTypes.scrape_config;
-      default = [];
+      default = [ ];
       description = ''
         A list of scrape configurations.
       '';
@@ -833,7 +1686,7 @@ in {
 
     alertmanagers = mkOption {
       type = types.listOf types.attrs;
-      example = literalExample ''
+      example = literalExpression ''
         [ {
           scheme = "https";
           path_prefix = "/alertmanager";
@@ -844,7 +1697,7 @@ in {
           } ];
         } ]
       '';
-      default = [];
+      default = [ ];
       description = ''
         A list of alertmanagers to send alerts to.
         See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config">the official documentation</link> for more information.
@@ -903,11 +1756,13 @@ in {
 
   config = mkIf cfg.enable {
     assertions = [
-      ( let
+      (
+        let
           # Match something with dots (an IPv4 address) or something ending in
           # a square bracket (an IPv6 addresses) followed by a port number.
           legacy = builtins.match "(.*\\..*|.*]):([[:digit:]]+)" cfg.listenAddress;
-        in {
+        in
+        {
           assertion = legacy == null;
           message = ''
             Do not specify the port for Prometheus to listen on in the
@@ -925,20 +1780,19 @@ in {
       uid = config.ids.uids.prometheus;
       group = "prometheus";
     };
+    environment.etc."prometheus/prometheus.yaml" = mkIf cfg.enableReload {
+      source = prometheusYml;
+    };
     systemd.services.prometheus = {
       wantedBy = [ "multi-user.target" ];
-      after    = [ "network.target" ];
-      preStart = ''
-         ${lib.getBin pkgs.envsubst}/bin/envsubst -o "/run/prometheus/prometheus-substituted.yaml" \
-                                                  -i "${prometheusYml}"
-      '';
+      after = [ "network.target" ];
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/prometheus" +
           optionalString (length cmdlineArgs != 0) (" \\\n  " +
             concatStringsSep " \\\n  " cmdlineArgs);
+        ExecReload = mkIf cfg.enableReload "+${reload}/bin/reload-prometheus";
         User = "prometheus";
-        Restart  = "always";
-        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+        Restart = "always";
         RuntimeDirectory = "prometheus";
         RuntimeDirectoryMode = "0700";
         WorkingDirectory = workingDir;
@@ -946,5 +1800,29 @@ in {
         StateDirectoryMode = "0700";
       };
     };
+    # prometheus-config-reload will activate after prometheus. However, what we
+    # don't want is that on startup it immediately reloads prometheus because
+    # prometheus itself might have just started.
+    #
+    # Instead we only want to reload prometheus when the config file has
+    # changed. So on startup prometheus-config-reload will just output a
+    # harmless message and then stay active (RemainAfterExit).
+    #
+    # Then, when the config file has changed, switch-to-configuration notices
+    # that this service has changed (restartTriggers) and needs to be reloaded
+    # (reloadIfChanged). The reload command then reloads prometheus.
+    systemd.services.prometheus-config-reload = mkIf cfg.enableReload {
+      wantedBy = [ "prometheus.service" ];
+      after = [ "prometheus.service" ];
+      reloadIfChanged = true;
+      restartTriggers = [ prometheusYml ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        TimeoutSec = 60;
+        ExecStart = "${pkgs.logger}/bin/logger 'prometheus-config-reload will only reload prometheus when reloaded itself.'";
+        ExecReload = [ "${triggerReload}/bin/trigger-reload-prometheus" ];
+      };
+    };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 9182c2f2ed87d..62e90232e114c 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -1,7 +1,7 @@
 { config, pkgs, lib, options, ... }:
 
 let
-  inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers
+  inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers
                 mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
                 optional types mkOptionDefault flip attrNames;
 
@@ -32,6 +32,7 @@ let
     "dnsmasq"
     "domain"
     "dovecot"
+    "fastly"
     "fritzbox"
     "influxdb"
     "json"
@@ -108,7 +109,7 @@ let
     firewallFilter = mkOption {
       type = types.nullOr types.str;
       default = null;
-      example = literalExample ''
+      example = literalExpression ''
         "-i eth0 -p tcp -m tcp --dport ${toString port}"
       '';
       description = ''
@@ -184,6 +185,28 @@ let
         serviceConfig.DynamicUser = mkDefault enableDynamicUser;
         serviceConfig.User = mkDefault conf.user;
         serviceConfig.Group = conf.group;
+        # Hardening
+        serviceConfig.CapabilityBoundingSet = mkDefault [ "" ];
+        serviceConfig.DeviceAllow = [ "" ];
+        serviceConfig.LockPersonality = true;
+        serviceConfig.MemoryDenyWriteExecute = true;
+        serviceConfig.NoNewPrivileges = true;
+        serviceConfig.PrivateDevices = true;
+        serviceConfig.ProtectClock = mkDefault true;
+        serviceConfig.ProtectControlGroups = true;
+        serviceConfig.ProtectHome = true;
+        serviceConfig.ProtectHostname = true;
+        serviceConfig.ProtectKernelLogs = true;
+        serviceConfig.ProtectKernelModules = true;
+        serviceConfig.ProtectKernelTunables = true;
+        serviceConfig.ProtectSystem = mkDefault "strict";
+        serviceConfig.RemoveIPC = true;
+        serviceConfig.RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        serviceConfig.RestrictNamespaces = true;
+        serviceConfig.RestrictRealtime = true;
+        serviceConfig.RestrictSUIDSGID = true;
+        serviceConfig.SystemCallArchitectures = "native";
+        serviceConfig.UMask = "0077";
       } serviceOpts ]);
   };
 in
@@ -203,7 +226,7 @@ in
     };
     description = "Prometheus exporter configuration";
     default = {};
-    example = literalExample ''
+    example = literalExpression ''
       {
         node = {
           enable = true;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
index d8a526eafcea9..1ef264fc86e5a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
@@ -41,6 +41,10 @@ in
           -format.new=${if cfg.newMetricFormat then "true" else "false"} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix b/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix
index 7557480ac0628..e9be39608fcbe 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix
@@ -36,7 +36,7 @@ in
     queues = mkOption {
       type = with types; nullOr (listOf str);
       default = null;
-      example = literalExample ''[ "my-queue1" "my-queue2" ]'';
+      example = literalExpression ''[ "my-queue1" "my-queue2" ]'';
       description = ''
         Which specific queues to process.
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
index 472652fe8a7a9..092ac6fea7d74 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -83,6 +83,10 @@ in
           --dovecot.scopes ${concatStringsSep "," cfg.scopes} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
new file mode 100644
index 0000000000000..5b35bb29a301a
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let cfg = config.services.prometheus.exporters.fastly;
+in
+{
+  port = 9118;
+  extraOpts = {
+    debug = mkEnableOption "Debug logging mode for fastly-exporter";
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Path to a fastly-exporter configuration file.
+        Example one can be generated with <literal>fastly-exporter --config-file-example</literal>.
+      '';
+      example = "./fastly-exporter-config.txt";
+    };
+
+    tokenPath = mkOption {
+      type = types.nullOr types.path;
+      apply = final: if final == null then null else toString final;
+      description = ''
+        A run-time path to the token file, which is supposed to be provisioned
+        outside of Nix store.
+      '';
+    };
+  };
+  serviceOpts = {
+    script = ''
+      ${optionalString (cfg.tokenPath != null)
+      "export FASTLY_API_TOKEN=$(cat ${toString cfg.tokenPath})"}
+      ${pkgs.fastly-exporter}/bin/fastly-exporter \
+        -endpoint http://${cfg.listenAddress}:${cfg.port}/metrics
+        ${optionalString cfg.debug "-debug true"} \
+        ${optionalString cfg.configFile "-config-file ${cfg.configFile}"}
+    '';
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/flow.nix b/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
index 6a35f46308fe9..b85e5461f2180 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
@@ -9,7 +9,7 @@ in {
   extraOpts = {
     brokers = mkOption {
       type = types.listOf types.str;
-      example = literalExample ''[ "kafka.example.org:19092" ]'';
+      example = literalExpression ''[ "kafka.example.org:19092" ]'';
       description = "List of Kafka brokers to connect to.";
     };
 
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index 9677281f87724..27aeb9096243c 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -13,7 +13,7 @@ in {
   extraOpts = {
     controlSocketPaths = mkOption {
       type = types.listOf types.str;
-      example = literalExample ''
+      example = literalExpression ''
         [
           "/run/kea/kea-dhcp4.socket"
           "/run/kea/kea-dhcp6.socket"
@@ -34,6 +34,10 @@ in {
           ${concatStringsSep " \\n" cfg.controlSocketPaths}
       '';
       SupplementaryGroups = [ "kea" ];
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
index 46c28fe0a5781..29e543f1013b6 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
@@ -10,7 +10,7 @@ in {
     knotLibraryPath = mkOption {
       type = types.str;
       default = "${pkgs.knot-dns.out}/lib/libknot.so";
-      defaultText = "\${pkgs.knot-dns}/lib/libknot.so";
+      defaultText = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
       description = ''
         Path to the library of <package>knot-dns</package>.
       '';
@@ -45,6 +45,10 @@ in {
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
       SupplementaryGroups = [ "knot" ];
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
index 7e196149fbb34..956bd96aa4543 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -100,7 +100,7 @@ let
     servers = mkOption {
       type = types.listOf (types.submodule serverOptions);
       default = [];
-      example = literalExample ''
+      example = literalExpression ''
         [ {
           name = "testserver";
           server = "smtp.domain.tld";
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix b/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
index 62c2cc5684764..8f9536b702a56 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
@@ -15,7 +15,7 @@ in
         Path to a mikrotik exporter configuration file. Mutually exclusive with
         <option>configuration</option> option.
       '';
-      example = literalExample "./mikrotik.yml";
+      example = literalExpression "./mikrotik.yml";
     };
 
     configuration = mkOption {
@@ -28,7 +28,7 @@ in
         See <link xlink:href="https://github.com/nshttpd/mikrotik-exporter/blob/master/README.md"/>
         for the description of the configuration file format.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         {
           devices = [
             {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix b/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
index 86ea98b94e4c2..afd03f6c270ec 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
@@ -28,6 +28,10 @@ in
           -rate ${cfg.refreshRate} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 5ee8c346be1dc..3cdd7866bd4db 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -47,7 +47,7 @@ in
       ExecStart = ''
         ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
           --nginx.scrape-uri '${cfg.scrapeUri}' \
-          --nginx.ssl-verify ${toString cfg.sslVerify} \
+          --nginx.ssl-verify ${boolToString cfg.sslVerify} \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
           --web.telemetry-path ${cfg.telemetryPath} \
           --prometheus.const-labels ${concatStringsSep "," cfg.constLabels} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index adc2abe0b91c8..5e5fc7cd55245 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/node.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -11,7 +11,7 @@ in
     enabledCollectors = mkOption {
       type = types.listOf types.str;
       default = [];
-      example = ''[ "systemd" ]'';
+      example = [ "systemd" ];
       description = ''
         Collectors to enable. The collectors listed here are enabled in addition to the default ones.
       '';
@@ -19,7 +19,7 @@ in
     disabledCollectors = mkOption {
       type = types.listOf types.str;
       default = [];
-      example = ''[ "timex" ]'';
+      example = [ "timex" ];
       description = ''
         Collectors to disable which are enabled by default.
       '';
@@ -35,6 +35,15 @@ in
           ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
       '';
+      RestrictAddressFamilies = optionals (any (collector: (collector == "logind" || collector == "systemd")) cfg.enabledCollectors) [
+        # needs access to dbus via unix sockets (logind/systemd)
+        "AF_UNIX"
+      ] ++ optionals (any (collector: (collector == "network_route" || collector == "wifi")) cfg.enabledCollectors) [
+        # needs netlink sockets for wireless collector
+        "AF_NETLINK"
+      ];
+      # The timex collector needs to access clock APIs
+      ProtectClock = any (collector: collector == "timex") cfg.disabledCollectors;
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix b/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
index 21c2e5eab4ca1..4bc27ebc32f8a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
@@ -42,8 +42,8 @@ in
     };
     piholePort = mkOption {
       type = types.port;
-      default = "80";
-      example = "443";
+      default = 80;
+      example = 443;
       description = ''
         The port pihole webinterface is reachable on
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
index dd3bec8ec16c7..3f9a32ef3995b 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
@@ -79,6 +79,10 @@ in
           --web.telemetry-path ${cfg.telemetryPath} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/process.nix b/nixos/modules/services/monitoring/prometheus/exporters/process.nix
index e3b3d18367fd0..1e9c402fb55bd 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/process.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/process.nix
@@ -11,14 +11,12 @@ in
   extraOpts = {
     settings.process_names = mkOption {
       type = types.listOf types.anything;
-      default = {};
-      example = literalExample ''
-        {
-          process_names = [
-            # Remove nix store path from process name
-            { name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
-          ];
-        }
+      default = [];
+      example = literalExpression ''
+        [
+          # Remove nix store path from process name
+          { name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
+        ]
       '';
       description = ''
         All settings expressed as an Nix attrset.
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
index 994670a376e76..ed985751e4287 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
@@ -62,8 +62,8 @@ in
       default = {
         host = config.networking.hostName;
       };
-      defaultText = "{ host = config.networking.hostName; }";
-      example = literalExample ''
+      defaultText = literalExpression "{ host = config.networking.hostName; }";
+      example = literalExpression ''
         {
           host = config.networking.hostName;
           custom_label = "some_value";
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix b/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
index 01e420db38978..ef829a1b7d02e 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
@@ -61,6 +61,11 @@ in
     serviceConfig = {
       # rtl-sdr udev rules make supported USB devices +rw by plugdev.
       SupplementaryGroups = "plugdev";
+      # rtl_433 needs rw access to the USB radio.
+      PrivateDevices = lib.mkForce false;
+      DeviceAllow = lib.mkForce "char-usb_device rw";
+      RestrictAddressFamilies = [ "AF_NETLINK" ];
+
       ExecStart = let
         matchers = (map (m:
           "--channel_matcher '${m.name},${toString m.channel},${m.location}'"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/script.nix b/nixos/modules/services/monitoring/prometheus/exporters/script.nix
index 104ab859f2ee0..a805a0ad335d2 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/script.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/script.nix
@@ -30,7 +30,7 @@ in
           };
         };
       });
-      example = literalExample ''
+      example = literalExpression ''
         {
           scripts = [
             { name = "sleep"; script = "sleep 5"; }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix b/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
index 0a7bb9c27be28..0181c341a7efb 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
@@ -45,6 +45,7 @@ in
   serviceOpts = {
     serviceConfig = {
       AmbientCapabilities = [ "CAP_NET_RAW" ];
+      CapabilityBoundingSet = [ "CAP_NET_RAW" ];
       ExecStart = ''
         ${pkgs.prometheus-smokeping-prober}/bin/smokeping_prober \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index 01276366e97b5..de42663e67f49 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -14,7 +14,7 @@ in
       description = ''
         Path to a snmp exporter configuration file. Mutually exclusive with 'configuration' option.
       '';
-      example = "./snmp.yml";
+      example = literalExpression "./snmp.yml";
     };
 
     configuration = mkOption {
@@ -23,16 +23,14 @@ in
       description = ''
         Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option.
       '';
-      example = ''
-        {
-          "default" = {
-            "version" = 2;
-            "auth" = {
-              "community" = "public";
-            };
+      example = {
+        "default" = {
+          "version" = 2;
+          "auth" = {
+            "community" = "public";
           };
         };
-      '';
+      };
     };
 
     logFormat = mkOption {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/sql.nix b/nixos/modules/services/monitoring/prometheus/exporters/sql.nix
index d9be724ebc036..3496fd9541f37 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/sql.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/sql.nix
@@ -99,6 +99,10 @@ in
           -config.file ${configFile} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix b/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix
index 0514469b8a61e..c0a50f07d7171 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix
@@ -13,6 +13,10 @@ in {
         ${pkgs.prometheus-systemd-exporter}/bin/systemd_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix b/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix
index 56a559531c142..cf0efddd340a9 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix
@@ -49,6 +49,10 @@ in
           ${optionalString (cfg.controlInterface != null) "--control-interface ${cfg.controlInterface}"} \
           ${toString cfg.extraFlags}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_UNIX to collect data
+        "AF_UNIX"
+      ];
     };
   }] ++ [
     (mkIf config.services.unbound.enable {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
index 04421fc2d25a8..d4aa69629ec89 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -52,6 +52,7 @@ in {
 
     serviceConfig = {
       AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+      CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
       ExecStart = ''
         ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
           -p ${toString cfg.port} \
@@ -61,6 +62,10 @@ in {
           ${optionalString cfg.withRemoteIp "-r"} \
           ${optionalString (cfg.wireguardConfig != null) "-n ${escapeShellArg cfg.wireguardConfig}"}
       '';
+      RestrictAddressFamilies = [
+        # Need AF_NETLINK to collect data
+        "AF_NETLINK"
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
index f8fcc3eb97eff..01b9937624367 100644
--- a/nixos/modules/services/monitoring/prometheus/pushgateway.nix
+++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -26,7 +26,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.prometheus-pushgateway;
-        defaultText = "pkgs.prometheus-pushgateway";
+        defaultText = literalExpression "pkgs.prometheus-pushgateway";
         description = ''
           Package that should be used for the prometheus pushgateway.
         '';
diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix
index ef535585e9be0..6a6fe110f9407 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -43,8 +43,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.scollector;
-        defaultText = "pkgs.scollector";
-        example = literalExample "pkgs.scollector";
+        defaultText = literalExpression "pkgs.scollector";
         description = ''
           scollector binary to use.
         '';
@@ -78,7 +77,7 @@ in {
       collectors = mkOption {
         type = with types; attrsOf (listOf path);
         default = {};
-        example = literalExample "{ \"0\" = [ \"\${postgresStats}/bin/collect-stats\" ]; }";
+        example = literalExpression ''{ "0" = [ "''${postgresStats}/bin/collect-stats" ]; }'';
         description = ''
           An attribute set mapping the frequency of collection to a list of
           binaries that should be executed at that frequency. You can use "0"
diff --git a/nixos/modules/services/monitoring/teamviewer.nix b/nixos/modules/services/monitoring/teamviewer.nix
index ce9e57a187cdc..e2271e571c40c 100644
--- a/nixos/modules/services/monitoring/teamviewer.nix
+++ b/nixos/modules/services/monitoring/teamviewer.nix
@@ -24,18 +24,21 @@ in
 
     environment.systemPackages = [ pkgs.teamviewer ];
 
+    services.dbus.packages = [ pkgs.teamviewer ];
+
     systemd.services.teamviewerd = {
       description = "TeamViewer remote control daemon";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "NetworkManager-wait-online.service" "network.target" ];
+      after = [ "NetworkManager-wait-online.service" "network.target" "dbus.service" ];
+      requires = [ "dbus.service" ];
       preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer";
 
       startLimitIntervalSec = 60;
       startLimitBurst = 10;
       serviceConfig = {
-        Type = "forking";
-        ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -d";
+        Type = "simple";
+        ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -f";
         PIDFile = "/run/teamviewerd.pid";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         Restart = "on-abort";
diff --git a/nixos/modules/services/monitoring/telegraf.nix b/nixos/modules/services/monitoring/telegraf.nix
index 4046260c16493..13aae58d0f37d 100644
--- a/nixos/modules/services/monitoring/telegraf.nix
+++ b/nixos/modules/services/monitoring/telegraf.nix
@@ -15,7 +15,7 @@ in {
 
       package = mkOption {
         default = pkgs.telegraf;
-        defaultText = "pkgs.telegraf";
+        defaultText = literalExpression "pkgs.telegraf";
         description = "Which telegraf derivation to use";
         type = types.package;
       };
@@ -23,7 +23,7 @@ in {
       environmentFiles = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = "/run/keys/telegraf.env";
+        example = [ "/run/keys/telegraf.env" ];
         description = ''
           File to load as environment file. Environment variables from this file
           will be interpolated into the config file using envsubst with this
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
index 96addf392bd79..da626788d827c 100644
--- a/nixos/modules/services/monitoring/thanos.nix
+++ b/nixos/modules/services/monitoring/thanos.nix
@@ -120,7 +120,7 @@ let
           type = with types; nullOr str;
           default = if cfg.tracing.config == null then null
                     else toString (toYAML "tracing.yaml" cfg.tracing.config);
-          defaultText = ''
+          defaultText = literalExpression ''
             if config.services.thanos.<cmd>.tracing.config == null then null
             else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
           '';
@@ -185,7 +185,7 @@ let
           type = with types; nullOr str;
           default = if cfg.objstore.config == null then null
                     else toString (toYAML "objstore.yaml" cfg.objstore.config);
-          defaultText = ''
+          defaultText = literalExpression ''
             if config.services.thanos.<cmd>.objstore.config == null then null
             else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
           '';
@@ -227,7 +227,7 @@ let
         option = mkOption {
           type = types.str;
           default = "/var/lib/${config.services.prometheus.stateDir}/data";
-          defaultText = "/var/lib/\${config.services.prometheus.stateDir}/data";
+          defaultText = literalExpression ''"/var/lib/''${config.services.prometheus.stateDir}/data"'';
           description = ''
             Data directory of TSDB.
           '';
@@ -656,7 +656,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.thanos;
-      defaultText = "pkgs.thanos";
+      defaultText = literalExpression "pkgs.thanos";
       description = ''
         The thanos package that should be used.
       '';
diff --git a/nixos/modules/services/monitoring/tuptime.nix b/nixos/modules/services/monitoring/tuptime.nix
index 17c5c1f56eaf6..de80282559aeb 100644
--- a/nixos/modules/services/monitoring/tuptime.nix
+++ b/nixos/modules/services/monitoring/tuptime.nix
@@ -36,6 +36,7 @@ in {
       groups._tuptime.members = [ "_tuptime" ];
       users._tuptime = {
         isSystemUser = true;
+        group = "_tuptime";
         description = "tuptime database owner";
       };
     };
diff --git a/nixos/modules/services/monitoring/unifi-poller.nix b/nixos/modules/services/monitoring/unifi-poller.nix
index 208f5e4875b40..cca4a0e720714 100644
--- a/nixos/modules/services/monitoring/unifi-poller.nix
+++ b/nixos/modules/services/monitoring/unifi-poller.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.unifi-poller;
 
   configFile = pkgs.writeText "unifi-poller.json" (generators.toJSON {} {
-    inherit (cfg) poller influxdb prometheus unifi;
+    inherit (cfg) poller influxdb loki prometheus unifi;
   });
 
 in {
@@ -87,7 +87,7 @@ in {
       pass = mkOption {
         type = types.path;
         default = pkgs.writeText "unifi-poller-influxdb-default.password" "unifipoller";
-        defaultText = "unifi-poller-influxdb-default.password";
+        defaultText = literalExpression "unifi-poller-influxdb-default.password";
         description = ''
           Path of a file containing the password for influxdb.
           This file needs to be readable by the unifi-poller user.
@@ -118,6 +118,61 @@ in {
       };
     };
 
+    loki = {
+      url = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          URL of the Loki host.
+        '';
+      };
+      user = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Username for Loki.
+        '';
+      };
+      pass = mkOption {
+        type = types.path;
+        default = pkgs.writeText "unifi-poller-loki-default.password" "";
+        defaultText = "unifi-poller-influxdb-default.password";
+        description = ''
+          Path of a file containing the password for Loki.
+          This file needs to be readable by the unifi-poller user.
+        '';
+        apply = v: "file://${v}";
+      };
+      verify_ssl = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Verify Loki's certificate.
+        '';
+      };
+      tenant_id = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Tenant ID to use in Loki.
+        '';
+      };
+      interval = mkOption {
+        type = types.str;
+        default = "2m";
+        description = ''
+          How often the events are polled and pushed to Loki.
+        '';
+      };
+      timeout = mkOption {
+        type = types.str;
+        default = "10s";
+        description = ''
+          Should be increased in case of timeout errors.
+        '';
+      };
+    };
+
     unifi = let
       controllerOptions = {
         user = mkOption {
@@ -130,7 +185,7 @@ in {
         pass = mkOption {
           type = types.path;
           default = pkgs.writeText "unifi-poller-unifi-default.password" "unifi";
-          defaultText = "unifi-poller-unifi-default.password";
+          defaultText = literalExpression "unifi-poller-unifi-default.password";
           description = ''
             Path of a file containing the password for the unifi service user.
             This file needs to be readable by the unifi-poller user.
@@ -157,7 +212,28 @@ in {
           type = types.bool;
           default = false;
           description = ''
-            Collect and save data from the intrusion detection system to influxdb.
+            Collect and save data from the intrusion detection system to influxdb and Loki.
+          '';
+        };
+        save_events = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Collect and save data from UniFi events to influxdb and Loki.
+          '';
+        };
+        save_alarms = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Collect and save data from UniFi alarms to influxdb and Loki.
+          '';
+        };
+        save_anomalies = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Collect and save data from UniFi anomalies to influxdb and Loki.
           '';
         };
         save_dpi = mkOption {
diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index 7eb6449e384df..c48b973f1ef7e 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -4,7 +4,7 @@ let
   cfg = config.services.zabbixAgent;
 
   inherit (lib) mkDefault mkEnableOption mkIf mkMerge mkOption;
-  inherit (lib) attrValues concatMapStringsSep literalExample optionalString types;
+  inherit (lib) attrValues concatMapStringsSep literalExpression optionalString types;
   inherit (lib.generators) toKeyValue;
 
   user = "zabbix-agent";
@@ -34,15 +34,15 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.zabbix.agent;
-        defaultText = "pkgs.zabbix.agent";
+        defaultText = literalExpression "pkgs.zabbix.agent";
         description = "The Zabbix package to use.";
       };
 
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = with pkgs; [ nettools ];
-        defaultText = "[ nettools ]";
-        example = "[ nettools mysql ]";
+        defaultText = literalExpression "with pkgs; [ nettools ]";
+        example = literalExpression "with pkgs; [ nettools mysql ]";
         description = ''
           Packages to be added to the Zabbix <envar>PATH</envar>.
           Typically used to add executables for scripts, but can be anything.
@@ -53,7 +53,7 @@ in
         type = types.attrsOf types.package;
         description = "A set of modules to load.";
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "dummy.so" = pkgs.stdenv.mkDerivation {
               name = "zabbix-dummy-module-''${cfg.package.version}";
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
index 2c8b8b92cb38b..b5009f47f175c 100644
--- a/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -6,7 +6,7 @@ let
   mysql = config.services.mysql;
 
   inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption;
-  inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types;
+  inherit (lib) attrValues concatMapStringsSep getName literalExpression optional optionalAttrs optionalString types;
   inherit (lib.generators) toKeyValue;
 
   user = "zabbix";
@@ -52,14 +52,14 @@ in
           if cfg.database.type == "mysql" then pkgs.zabbix.proxy-mysql
           else if cfg.database.type == "pgsql" then pkgs.zabbix.proxy-pgsql
           else pkgs.zabbix.proxy-sqlite;
-        defaultText = "pkgs.zabbix.proxy-pgsql";
+        defaultText = literalExpression "pkgs.zabbix.proxy-pgsql";
         description = "The Zabbix package to use.";
       };
 
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = with pkgs; [ nettools nmap traceroute ];
-        defaultText = "[ nettools nmap traceroute ]";
+        defaultText = literalExpression "[ nettools nmap traceroute ]";
         description = ''
           Packages to be added to the Zabbix <envar>PATH</envar>.
           Typically used to add executables for scripts, but can be anything.
@@ -70,7 +70,7 @@ in
         type = types.attrsOf types.package;
         description = "A set of modules to load.";
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "dummy.so" = pkgs.stdenv.mkDerivation {
               name = "zabbix-dummy-module-''${cfg.package.version}";
@@ -109,7 +109,7 @@ in
         name = mkOption {
           type = types.str;
           default = if cfg.database.type == "sqlite" then "${stateDir}/zabbix.db" else "zabbix";
-          defaultText = "zabbix";
+          defaultText = literalExpression "zabbix";
           description = "Database name.";
         };
 
@@ -262,7 +262,12 @@ in
     };
 
     security.wrappers = {
-      fping.source = "${pkgs.fping}/bin/fping";
+      fping =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.fping}/bin/fping";
+        };
     };
 
     systemd.services.zabbix-proxy = {
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index c8658634ecb62..0141c073da25d 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -6,7 +6,7 @@ let
   mysql = config.services.mysql;
 
   inherit (lib) mkAfter mkDefault mkEnableOption mkIf mkMerge mkOption;
-  inherit (lib) attrValues concatMapStringsSep getName literalExample optional optionalAttrs optionalString types;
+  inherit (lib) attrValues concatMapStringsSep getName literalExpression optional optionalAttrs optionalString types;
   inherit (lib.generators) toKeyValue;
 
   user = "zabbix";
@@ -44,14 +44,14 @@ in
       package = mkOption {
         type = types.package;
         default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql;
-        defaultText = "pkgs.zabbix.server-pgsql";
+        defaultText = literalExpression "pkgs.zabbix.server-pgsql";
         description = "The Zabbix package to use.";
       };
 
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = with pkgs; [ nettools nmap traceroute ];
-        defaultText = "[ nettools nmap traceroute ]";
+        defaultText = literalExpression "[ nettools nmap traceroute ]";
         description = ''
           Packages to be added to the Zabbix <envar>PATH</envar>.
           Typically used to add executables for scripts, but can be anything.
@@ -62,7 +62,7 @@ in
         type = types.attrsOf types.package;
         description = "A set of modules to load.";
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             "dummy.so" = pkgs.stdenv.mkDerivation {
               name = "zabbix-dummy-module-''${cfg.package.version}";
@@ -250,7 +250,12 @@ in
     };
 
     security.wrappers = {
-      fping.source = "${pkgs.fping}/bin/fping";
+      fping =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.fping}/bin/fping";
+        };
     };
 
     systemd.services.zabbix-server = {
diff --git a/nixos/modules/services/network-filesystems/ceph.nix b/nixos/modules/services/network-filesystems/ceph.nix
index d833062c47370..e313589134f10 100644
--- a/nixos/modules/services/network-filesystems/ceph.nix
+++ b/nixos/modules/services/network-filesystems/ceph.nix
@@ -97,6 +97,7 @@ in
       mgrModulePath = mkOption {
         type = types.path;
         default = "${pkgs.ceph.lib}/lib/ceph/mgr";
+        defaultText = literalExpression ''"''${pkgs.ceph.lib}/lib/ceph/mgr"'';
         description = ''
           Path at which to find ceph-mgr modules.
         '';
@@ -181,6 +182,7 @@ in
       rgwMimeTypesFile = mkOption {
         type = with types; nullOr path;
         default = "${pkgs.mime-types}/etc/mime.types";
+        defaultText = literalExpression ''"''${pkgs.mime-types}/etc/mime.types"'';
         description = ''
           Path to mime types used by radosgw.
         '';
@@ -190,11 +192,9 @@ in
     extraConfig = mkOption {
       type = with types; attrsOf str;
       default = {};
-      example = ''
-        {
-          "ms bind ipv6" = "true";
-        };
-      '';
+      example = {
+        "ms bind ipv6" = "true";
+      };
       description = ''
         Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
       '';
@@ -205,9 +205,7 @@ in
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
-        example = ''
-          [ "name1" "name2" ];
-        '';
+        example = [ "name1" "name2" ];
         description = ''
           A list of names for manager daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
@@ -227,9 +225,7 @@ in
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
-        example = ''
-          [ "name1" "name2" ];
-        '';
+        example = [ "name1" "name2" ];
         description = ''
           A list of monitor daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in mon.name1
@@ -249,9 +245,7 @@ in
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
-        example = ''
-          [ "name1" "name2" ];
-        '';
+        example = [ "name1" "name2" ];
         description = ''
           A list of OSD daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in osd.name1
@@ -279,9 +273,7 @@ in
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
-        example = ''
-          [ "name1" "name2" ];
-        '';
+        example = [ "name1" "name2" ];
         description = ''
           A list of metadata service daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in mds.name1
@@ -301,9 +293,7 @@ in
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
-        example = ''
-          [ "name1" "name2" ];
-        '';
+        example = [ "name1" "name2" ];
         description = ''
           A list of rados gateway daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
@@ -318,7 +308,7 @@ in
       extraConfig = mkOption {
         type = with types; attrsOf (attrsOf str);
         default = {};
-        example = ''
+        example = literalExpression ''
           {
             # This would create a section for a radosgw daemon named node0 and related
             # configuration for it
diff --git a/nixos/modules/services/network-filesystems/glusterfs.nix b/nixos/modules/services/network-filesystems/glusterfs.nix
index d70092999f674..bc8be05ca8cb1 100644
--- a/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -113,19 +113,16 @@ in
         type = types.nullOr (types.submodule {
           options = {
             tlsKeyPath = mkOption {
-              default = null;
               type = types.str;
               description = "Path to the private key used for TLS.";
             };
 
             tlsPem = mkOption {
-              default = null;
               type = types.path;
               description = "Path to the certificate used for TLS.";
             };
 
             caCert = mkOption {
-              default = null;
               type = types.path;
               description = "Path certificate authority used to sign the cluster certificates.";
             };
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 57f5f6b006c8c..36b72ca48b2c3 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -12,6 +12,11 @@ let
     (optionalString (cfg.defaultMode == "norouting") "--routing=none")
   ] ++ cfg.extraFlags);
 
+  profile =
+    if cfg.localDiscovery
+    then "local-discovery"
+    else "server";
+
   splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw);
 
   multiaddrToListenStream = addrRaw:
@@ -52,7 +57,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.ipfs;
-        defaultText = "pkgs.ipfs";
+        defaultText = literalExpression "pkgs.ipfs";
         description = "Which IPFS package to use.";
       };
 
@@ -173,7 +178,7 @@ in
         description = ''Whether to enable local discovery for the ipfs daemon.
           This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
         '';
-        default = true;
+        default = false;
       };
 
       serviceFdlimit = mkOption {
@@ -236,14 +241,13 @@ in
       environment.IPFS_PATH = cfg.dataDir;
 
       preStart = ''
-        if [[ ! -f ${cfg.dataDir}/config ]]; then
-          ipfs init ${optionalString cfg.emptyRepo "-e"} \
-            ${optionalString (! cfg.localDiscovery) "--profile=server"}
+        if [[ ! -f "$IPFS_PATH/config" ]]; then
+          ipfs init ${optionalString cfg.emptyRepo "-e"} --profile=${profile}
         else
-          ${if cfg.localDiscovery
-            then "ipfs --offline config profile apply local-discovery"
-            else "ipfs --offline config profile apply server"
-          }
+          # After an unclean shutdown this file may exist which will cause the config command to attempt to talk to the daemon. This will hang forever if systemd is holding our sockets open.
+          rm -vf "$IPFS_PATH/api"
+
+          ipfs --offline config profile apply ${profile}
         fi
       '' + optionalString cfg.autoMount ''
         ipfs --offline config Mounts.FuseAllowOther --json true
diff --git a/nixos/modules/services/network-filesystems/litestream/default.nix b/nixos/modules/services/network-filesystems/litestream/default.nix
index f1806c5af0a94..51eb920d778dc 100644
--- a/nixos/modules/services/network-filesystems/litestream/default.nix
+++ b/nixos/modules/services/network-filesystems/litestream/default.nix
@@ -13,7 +13,7 @@ in
     package = mkOption {
       description = "Package to use.";
       default = pkgs.litestream;
-      defaultText = "pkgs.litestream";
+      defaultText = literalExpression "pkgs.litestream";
       type = types.package;
     };
 
diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix
index 03884cb729760..c8cc5052c2ace 100644
--- a/nixos/modules/services/network-filesystems/openafs/client.nix
+++ b/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -4,7 +4,7 @@
 with import ./lib.nix { inherit config lib pkgs; };
 
 let
-  inherit (lib) getBin mkOption mkIf optionalString singleton types;
+  inherit (lib) getBin literalExpression mkOption mkIf optionalString singleton types;
 
   cfg = config.services.openafsClient;
 
@@ -57,11 +57,10 @@ in
           CellServDB. See CellServDB(5) man page for syntax. Ignored when
           <literal>afsdb</literal> is set to <literal>true</literal>.
         '';
-        example = ''
-          [ { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
-            { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; }
-          ]
-        '';
+        example = [
+          { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
+          { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; }
+        ];
       };
 
       cache = {
@@ -149,13 +148,13 @@ in
       packages = {
         module = mkOption {
           default = config.boot.kernelPackages.openafs;
-          defaultText = "config.boot.kernelPackages.openafs";
+          defaultText = literalExpression "config.boot.kernelPackages.openafs";
           type = types.package;
           description = "OpenAFS kernel module package. MUST match the userland package!";
         };
         programs = mkOption {
           default = getBin pkgs.openafs;
-          defaultText = "getBin pkgs.openafs";
+          defaultText = literalExpression "getBin pkgs.openafs";
           type = types.package;
           description = "OpenAFS programs package. MUST match the kernel module package!";
         };
diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix
index 4fce650b01336..c1bf83be77b91 100644
--- a/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -4,7 +4,7 @@
 with import ./lib.nix { inherit config lib pkgs; };
 
 let
-  inherit (lib) concatStringsSep mkIf mkOption optionalString types;
+  inherit (lib) concatStringsSep literalExpression mkIf mkOption optionalString types;
 
   bosConfig = pkgs.writeText "BosConfig" (''
     restrictmode 1
@@ -81,7 +81,7 @@ in {
 
       package = mkOption {
         default = pkgs.openafs.server or pkgs.openafs;
-        defaultText = "pkgs.openafs.server or pkgs.openafs";
+        defaultText = literalExpression "pkgs.openafs.server or pkgs.openafs";
         type = types.package;
         description = "OpenAFS package for the server binaries";
       };
diff --git a/nixos/modules/services/network-filesystems/orangefs/client.nix b/nixos/modules/services/network-filesystems/orangefs/client.nix
index b69d9e713c3dc..36ea5af2168d0 100644
--- a/nixos/modules/services/network-filesystems/orangefs/client.nix
+++ b/nixos/modules/services/network-filesystems/orangefs/client.nix
@@ -47,7 +47,6 @@ in {
 
             target = mkOption {
               type = types.str;
-              default = null;
               example = "tcp://server:3334/orangefs";
               description = "Target URL";
             };
diff --git a/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixos/modules/services/network-filesystems/orangefs/server.nix
index 8eb754fe61103..621c2fe8f78df 100644
--- a/nixos/modules/services/network-filesystems/orangefs/server.nix
+++ b/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -118,12 +118,10 @@ in {
       servers = mkOption {
         type = with types; attrsOf types.str;
         default = {};
-        example = ''
-          {
-            node1="tcp://node1:3334";
-            node2="tcp://node2:3334";
-          }
-        '';
+        example = {
+          node1 = "tcp://node1:3334";
+          node2 = "tcp://node2:3334";
+        };
         description = "URLs for storage server including port. The attribute names define the server alias.";
       };
 
@@ -132,8 +130,7 @@ in {
           These options will create the <literal>&lt;FileSystem&gt;</literal> sections of config file.
         '';
         default = { orangefs = {}; };
-        defaultText = literalExample "{ orangefs = {}; }";
-        example = literalExample ''
+        example = literalExpression ''
           {
             fs1 = {
               id = 101;
@@ -193,7 +190,10 @@ in {
     environment.systemPackages = [ pkgs.orangefs ];
 
     # orangefs daemon will run as user
-    users.users.orangefs.isSystemUser = true;
+    users.users.orangefs = {
+      isSystemUser = true;
+      group = "orangfs";
+    };
     users.groups.orangefs = {};
 
     # To format the file system the config file is needed.
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 78ea245cb3519..9ed755d0465c4 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -87,13 +87,20 @@ in
           <note>
             <para>If you use the firewall consider adding the following:</para>
           <programlisting>
-            networking.firewall.allowedTCPPorts = [ 139 445 ];
-            networking.firewall.allowedUDPPorts = [ 137 138 ];
+            services.samba.openFirewall = true;
           </programlisting>
           </note>
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to automatically open the necessary ports in the firewall.
+        '';
+      };
+
       enableNmbd = mkOption {
         type = types.bool;
         default = true;
@@ -117,8 +124,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.samba;
-        defaultText = "pkgs.samba";
-        example = literalExample "pkgs.samba4Full";
+        defaultText = literalExpression "pkgs.samba";
+        example = literalExpression "pkgs.samba4Full";
         description = ''
           Defines which package should be used for the samba server.
         '';
@@ -176,7 +183,7 @@ in
           See <command>man smb.conf</command> for options.
         '';
         type = types.attrsOf (types.attrsOf types.unspecified);
-        example = literalExample ''
+        example = literalExpression ''
           { public =
             { path = "/srv/public";
               "read only" = true;
@@ -235,7 +242,10 @@ in
         };
 
         security.pam.services.samba = {};
-        environment.systemPackages = [ config.services.samba.package ];
+        environment.systemPackages = [ cfg.package ];
+
+        networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ 139 445 ];
+        networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 137 138 ];
       })
     ];
 
diff --git a/nixos/modules/services/network-filesystems/tahoe.nix b/nixos/modules/services/network-filesystems/tahoe.nix
index 7d75eb286106b..5426463dffaca 100644
--- a/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixos/modules/services/network-filesystems/tahoe.nix
@@ -34,9 +34,8 @@ in
             };
             package = mkOption {
               default = pkgs.tahoelafs;
-              defaultText = "pkgs.tahoelafs";
+              defaultText = literalExpression "pkgs.tahoelafs";
               type = types.package;
-              example = literalExample "pkgs.tahoelafs";
               description = ''
                 The package to use for the Tahoe LAFS daemon.
               '';
@@ -179,9 +178,8 @@ in
             };
             package = mkOption {
               default = pkgs.tahoelafs;
-              defaultText = "pkgs.tahoelafs";
+              defaultText = literalExpression "pkgs.tahoelafs";
               type = types.package;
-              example = literalExample "pkgs.tahoelafs";
               description = ''
                 The package to use for the Tahoe LAFS daemon.
               '';
diff --git a/nixos/modules/services/network-filesystems/webdav.nix b/nixos/modules/services/network-filesystems/webdav.nix
new file mode 100644
index 0000000000000..4086a0f5d5620
--- /dev/null
+++ b/nixos/modules/services/network-filesystems/webdav.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.webdav;
+  format = pkgs.formats.yaml { };
+in
+{
+  options = {
+    services.webdav = {
+      enable = mkEnableOption "WebDAV server";
+
+      user = mkOption {
+        type = types.str;
+        default = "webdav";
+        description = "User account under which WebDAV runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "webdav";
+        description = "Group under which WebDAV runs.";
+      };
+
+      settings = mkOption {
+        type = format.type;
+        default = { };
+        description = ''
+          Attrset that is converted and passed as config file. Available options
+          can be found at
+          <link xlink:href="https://github.com/hacdias/webdav">here</link>.
+
+          This program supports reading username and password configuration
+          from environment variables, so it's strongly recommended to store
+          username and password in a separate
+          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#EnvironmentFile=">EnvironmentFile</link>.
+          This prevents adding secrets to the world-readable Nix store.
+        '';
+        example = literalExpression ''
+          {
+              address = "0.0.0.0";
+              port = 8080;
+              scope = "/srv/public";
+              modify = true;
+              auth = true;
+              users = [
+                {
+                  username = "{env}ENV_USERNAME";
+                  password = "{env}ENV_PASSWORD";
+                }
+              ];
+          }
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = format.generate "webdav.yaml" cfg.settings;
+        defaultText = "Config file generated from services.webdav.settings";
+        description = ''
+          Path to config file. If this option is set, it will override any
+          configuration done in options.services.webdav.settings.
+        '';
+        example = "/etc/webdav/config.yaml";
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Environment file as defined in <citerefentry>
+          <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
+          </citerefentry>.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users = mkIf (cfg.user == "webdav") {
+      webdav = {
+        description = "WebDAV daemon user";
+        isSystemUser = true;
+        group = cfg.group;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "webdav") {
+      webdav = { };
+    };
+
+    systemd.services.webdav = {
+      description = "WebDAV server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.webdav}/bin/webdav -c ${cfg.configFile}";
+        Restart = "on-failure";
+        User = cfg.user;
+        Group = cfg.group;
+        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ pengmeiyu ];
+}
diff --git a/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixos/modules/services/network-filesystems/xtreemfs.nix
index 6cc8a05ee00b0..fc07231157877 100644
--- a/nixos/modules/services/network-filesystems/xtreemfs.nix
+++ b/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -142,7 +142,7 @@ in
           '';
         };
         syncMode = mkOption {
-          type = types.enum [ "ASYNC" "SYNC_WRITE_METADATA" "SYNC_WRITE" "FDATASYNC" "ASYNC" ];
+          type = types.enum [ "ASYNC" "SYNC_WRITE_METADATA" "SYNC_WRITE" "FDATASYNC" "FSYNC" ];
           default = "FSYNC";
           example = "FDATASYNC";
           description = ''
@@ -268,7 +268,7 @@ in
         };
         syncMode = mkOption {
           default = "FSYNC";
-          type = types.enum [ "ASYNC" "SYNC_WRITE_METADATA" "SYNC_WRITE" "FDATASYNC" "ASYNC" ];
+          type = types.enum [ "ASYNC" "SYNC_WRITE_METADATA" "SYNC_WRITE" "FDATASYNC" "FSYNC" ];
           example = "FDATASYNC";
           description = ''
             The sync mode influences how operations are committed to the disk
diff --git a/nixos/modules/services/networking/3proxy.nix b/nixos/modules/services/networking/3proxy.nix
index 37a48657c1cc9..326a8671fccad 100644
--- a/nixos/modules/services/networking/3proxy.nix
+++ b/nixos/modules/services/networking/3proxy.nix
@@ -205,7 +205,7 @@ in {
               };
             });
             default = [ ];
-            example = literalExample ''
+            example = literalExpression ''
               [
                 {
                   rule = "allow";
@@ -244,7 +244,7 @@ in {
         };
       });
       default = [ ];
-      example = literalExample ''
+      example = literalExpression ''
         [
           {
             type = "proxy";
@@ -290,17 +290,6 @@ in {
         "::1"
         "fc00::/7"
       ];
-      example = [
-        "0.0.0.0/8"
-        "127.0.0.0/8"
-        "10.0.0.0/8"
-        "100.64.0.0/10"
-        "172.16.0.0/12"
-        "192.168.0.0/16"
-        "::"
-        "::1"
-        "fc00::/7"
-      ];
       description = ''
         What IP ranges to deny access when denyPrivate is set tu true.
       '';
@@ -322,19 +311,17 @@ in {
           nscache = mkOption {
             type = types.int;
             default = 65535;
-            example = 65535;
             description = "Set name cache size for IPv4.";
           };
           nscache6 = mkOption {
             type = types.int;
             default = 65535;
-            example = 65535;
             description = "Set name cache size for IPv6.";
           };
           nsrecord = mkOption {
             type = types.attrsOf types.str;
             default = { };
-            example = literalExample ''
+            example = literalExpression ''
               {
                 "files.local" = "192.168.1.12";
                 "site.local" = "192.168.1.43";
diff --git a/nixos/modules/services/networking/antennas.nix b/nixos/modules/services/networking/antennas.nix
new file mode 100644
index 0000000000000..ef98af22f20f2
--- /dev/null
+++ b/nixos/modules/services/networking/antennas.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.antennas;
+in
+
+{
+  options = {
+    services.antennas = {
+      enable = mkEnableOption "Antennas";
+
+      tvheadendUrl = mkOption {
+        type        = types.str;
+        default     = "http://localhost:9981";
+        description = "URL of Tvheadend.";
+      };
+
+      antennasUrl = mkOption {
+        type        = types.str;
+        default     = "http://127.0.0.1:5004";
+        description = "URL of Antennas.";
+      };
+
+      tunerCount = mkOption {
+        type        = types.int;
+        default     = 6;
+        description = "Numbers of tuners in tvheadend.";
+      };
+
+      deviceUUID = mkOption {
+        type        = types.str;
+        default     = "2f70c0d7-90a3-4429-8275-cbeeee9cd605";
+        description = "Device tuner UUID. Change this if you are running multiple instances.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.antennas = {
+      description = "Antennas HDHomeRun emulator for Tvheadend. ";
+      wantedBy    = [ "multi-user.target" ];
+
+      # Config
+      environment = {
+        TVHEADEND_URL = cfg.tvheadendUrl;
+        ANTENNAS_URL = cfg.antennasUrl;
+        TUNER_COUNT = toString cfg.tunerCount;
+        DEVICE_UUID = cfg.deviceUUID;
+      };
+
+      serviceConfig = {
+         ExecStart = "${pkgs.antennas}/bin/antennas";
+
+        # Antennas expects all resources like html and config to be relative to it's working directory
+        WorkingDirectory = "${pkgs.antennas}/libexec/antennas/deps/antennas/";
+
+        # Hardening
+        CapabilityBoundingSet = [ "" ];
+        DynamicUser = true;
+        LockPersonality = true;
+        ProcSubset = "pid";
+        PrivateDevices = true;
+        PrivateUsers = true;
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/asterisk.nix b/nixos/modules/services/networking/asterisk.nix
index 03a2544b9a7ea..af091d55c01b8 100644
--- a/nixos/modules/services/networking/asterisk.nix
+++ b/nixos/modules/services/networking/asterisk.nix
@@ -115,7 +115,7 @@ in
       confFiles = mkOption {
         default = {};
         type = types.attrsOf types.str;
-        example = literalExample
+        example = literalExpression
           ''
             {
               "extensions.conf" = '''
@@ -200,7 +200,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.asterisk;
-        defaultText = "pkgs.asterisk";
+        defaultText = literalExpression "pkgs.asterisk";
         description = "The Asterisk package to use.";
       };
     };
diff --git a/nixos/modules/services/networking/atftpd.nix b/nixos/modules/services/networking/atftpd.nix
index e7fd48c99a85c..da5e305201f86 100644
--- a/nixos/modules/services/networking/atftpd.nix
+++ b/nixos/modules/services/networking/atftpd.nix
@@ -28,7 +28,7 @@ in
       extraOptions = mkOption {
         default = [];
         type = types.listOf types.str;
-        example = literalExample ''
+        example = literalExpression ''
           [ "--bind-address 192.168.9.1"
             "--verbose=7"
           ]
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 020a817f25961..50c4ffdedce8d 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -54,7 +54,7 @@ in
     hostName = mkOption {
       type = types.str;
       default = config.networking.hostName;
-      defaultText = literalExample "config.networking.hostName";
+      defaultText = literalExpression "config.networking.hostName";
       description = ''
         Host name advertised on the LAN. If not set, avahi will use the value
         of <option>config.networking.hostName</option>.
@@ -87,7 +87,7 @@ in
     ipv6 = mkOption {
       type = types.bool;
       default = config.networking.enableIPv6;
-      defaultText = "config.networking.enableIPv6";
+      defaultText = literalExpression "config.networking.enableIPv6";
       description = "Whether to use IPv6.";
     };
 
@@ -134,7 +134,7 @@ in
     extraServiceFiles = mkOption {
       type = with types; attrsOf (either str path);
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
           smb = '''
diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix
index 8a77ce23ab4d6..d6efade0630ff 100644
--- a/nixos/modules/services/networking/bee.nix
+++ b/nixos/modules/services/networking/bee.nix
@@ -20,8 +20,8 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.bee;
-        defaultText = "pkgs.bee";
-        example = "pkgs.bee-unstable";
+        defaultText = literalExpression "pkgs.bee";
+        example = literalExpression "pkgs.bee-unstable";
         description = "The package providing the bee binary for the service.";
       };
 
diff --git a/nixos/modules/services/networking/biboumi.nix b/nixos/modules/services/networking/biboumi.nix
index 66ddca93d8181..3f46b95eaf0c0 100644
--- a/nixos/modules/services/networking/biboumi.nix
+++ b/nixos/modules/services/networking/biboumi.nix
@@ -107,6 +107,7 @@ in
           options.policy_directory = mkOption {
             type = types.path;
             default = "${pkgs.biboumi}/etc/biboumi";
+            defaultText = literalExpression ''"''${pkgs.biboumi}/etc/biboumi"'';
             description = ''
               A directory that should contain the policy files,
               used to customize Botan’s behaviour
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 480d5a184f250..f2b2e4c4d5d4f 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -110,7 +110,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.bind;
-        defaultText = "pkgs.bind";
+        defaultText = literalExpression "pkgs.bind";
         description = "The BIND package to use.";
       };
 
@@ -209,7 +209,7 @@ in
       configFile = mkOption {
         type = types.path;
         default = confFile;
-        defaultText = "confFile";
+        defaultText = literalExpression "confFile";
         description = "
           Overridable config file to use for named. By default, that
           generated by nixos.
@@ -229,9 +229,11 @@ in
 
     users.users.${bindUser} =
       {
-        uid = config.ids.uids.bind;
+        group = bindUser;
         description = "BIND daemon user";
+        isSystemUser = true;
       };
+    users.groups.${bindUser} = {};
 
     systemd.services.bind = {
       description = "BIND Domain Name Server";
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index bc9aa53f49aa7..80033d958609e 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -40,7 +40,7 @@ let
       package = mkOption {
         type = types.package;
         default = pkgs.bitcoind;
-        defaultText = "pkgs.bitcoind";
+        defaultText = literalExpression "pkgs.bitcoind";
         description = "The package providing bitcoin binaries.";
       };
 
@@ -88,7 +88,7 @@ let
         };
         users = mkOption {
           default = {};
-          example = literalExample ''
+          example = literalExpression ''
             {
               alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
               bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
diff --git a/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix
index 59ad9e5468631..8bf04e3a1a23c 100644
--- a/nixos/modules/services/networking/bitlbee.nix
+++ b/nixos/modules/services/networking/bitlbee.nix
@@ -16,7 +16,6 @@ let
     ''
     [settings]
     RunMode = Daemon
-    User = bitlbee
     ConfigDir = ${cfg.configDir}
     DaemonInterface = ${cfg.interface}
     DaemonPort = ${toString cfg.portNumber}
@@ -109,7 +108,7 @@ in
       plugins = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.bitlbee-facebook ]";
+        example = literalExpression "[ pkgs.bitlbee-facebook ]";
         description = ''
           The list of bitlbee plugins to install.
         '';
@@ -118,7 +117,7 @@ in
       libpurple_plugins = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.purple-matrix ]";
+        example = literalExpression "[ pkgs.purple-matrix ]";
         description = ''
           The list of libpurple plugins to install.
         '';
@@ -166,24 +165,17 @@ in
 
   config =  mkMerge [
     (mkIf config.services.bitlbee.enable {
-      users.users.bitlbee = {
-        uid = bitlbeeUid;
-        description = "BitlBee user";
-        home = "/var/lib/bitlbee";
-        createHome = true;
-      };
-
-      users.groups.bitlbee = {
-        gid = config.ids.gids.bitlbee;
-      };
-
       systemd.services.bitlbee = {
         environment.PURPLE_PLUGIN_PATH = purple_plugin_path;
         description = "BitlBee IRC to other chat networks gateway";
         after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
-        serviceConfig.User = "bitlbee";
-        serviceConfig.ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
+
+        serviceConfig = {
+          DynamicUser = true;
+          StateDirectory = "bitlbee";
+          ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
+        };
       };
 
       environment.systemPackages = [ bitlbeePkg ];
diff --git a/nixos/modules/services/networking/blockbook-frontend.nix b/nixos/modules/services/networking/blockbook-frontend.nix
index ca323e495ec1d..eeea521c8d510 100644
--- a/nixos/modules/services/networking/blockbook-frontend.nix
+++ b/nixos/modules/services/networking/blockbook-frontend.nix
@@ -15,6 +15,7 @@ let
       package = mkOption {
         type = types.package;
         default = pkgs.blockbook;
+        defaultText = literalExpression "pkgs.blockbook";
         description = "Which blockbook package to use.";
       };
 
@@ -50,7 +51,6 @@ let
       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).
@@ -60,7 +60,8 @@ let
       cssDir = mkOption {
         type = types.path;
         default = "${config.package}/share/css/";
-        example = "${config.dataDir}/static/css/";
+        defaultText = literalExpression ''"''${package}/share/css/"'';
+        example = literalExpression ''"''${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.
@@ -82,21 +83,18 @@ let
       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>.";
       };
 
@@ -116,14 +114,12 @@ let
         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!!!
@@ -150,14 +146,15 @@ let
       templateDir = mkOption {
         type = types.path;
         default = "${config.package}/share/templates/";
-        example = "${config.dataDir}/templates/static/";
+        defaultText = literalExpression ''"''${package}/share/templates/"'';
+        example = literalExpression ''"''${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 '' {
+        example = literalExpression '' {
           "alternative_estimate_fee" = "whatthefee-disabled";
           "alternative_estimate_fee_params" = "{\"url\": \"https://whatthefee.io/data.json\", \"periodSeconds\": 60}";
           "fiat_rates" = "coingecko";
diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix
index ca95d00c2ff80..0d97d379e907e 100644
--- a/nixos/modules/services/networking/cjdns.nix
+++ b/nixos/modules/services/networking/cjdns.nix
@@ -150,7 +150,7 @@ in
         connectTo = mkOption {
           type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
           default = { };
-          example = literalExample ''
+          example = literalExpression ''
             {
               "192.168.1.1:27313" = {
                 hostname = "homer.hype";
@@ -197,7 +197,7 @@ in
         connectTo = mkOption {
           type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
           default = { };
-          example = literalExample ''
+          example = literalExpression ''
             {
               "01:02:03:04:05:06" = {
                 hostname = "homer.hype";
diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix
index 608672c6446cd..8886e7a30f1fd 100644
--- a/nixos/modules/services/networking/connman.nix
+++ b/nixos/modules/services/networking/connman.nix
@@ -77,10 +77,11 @@ in {
       };
 
       package = mkOption {
-        type = types.path;
+        type = types.package;
         description = "The connman package / build flavor";
         default = connman;
-        example = literalExample "pkgs.connmanFull";
+        defaultText = literalExpression "pkgs.connman";
+        example = literalExpression "pkgs.connmanFull";
       };
 
     };
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index ae7998913ee08..792b2e7f5dfeb 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -34,7 +34,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.consul;
-        defaultText = "pkgs.consul";
+        defaultText = literalExpression "pkgs.consul";
         description = ''
           The package used for the Consul agent and CLI.
         '';
@@ -121,7 +121,7 @@ in
         package = mkOption {
           description = "Package to use for consul-alerts.";
           default = pkgs.consul-alerts;
-          defaultText = "pkgs.consul-alerts";
+          defaultText = literalExpression "pkgs.consul-alerts";
           type = types.package;
         };
 
@@ -159,10 +159,12 @@ in
 
       users.users.consul = {
         description = "Consul agent daemon user";
-        uid = config.ids.uids.consul;
+        isSystemUser = true;
+        group = "consul";
         # The shell is needed for health checks
         shell = "/run/current-system/sw/bin/bash";
       };
+      users.groups.consul = {};
 
       environment = {
         etc."consul.json".text = builtins.toJSON configOptions;
diff --git a/nixos/modules/services/networking/coredns.nix b/nixos/modules/services/networking/coredns.nix
index afb2b547a4655..88615d8e610f2 100644
--- a/nixos/modules/services/networking/coredns.nix
+++ b/nixos/modules/services/networking/coredns.nix
@@ -22,7 +22,7 @@ in {
 
     package = mkOption {
       default = pkgs.coredns;
-      defaultText = "pkgs.coredns";
+      defaultText = literalExpression "pkgs.coredns";
       type = types.package;
       description = "Coredns package to use.";
     };
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
index e76ba9a2d00dc..9d79d5d7686b2 100644
--- a/nixos/modules/services/networking/corerad.nix
+++ b/nixos/modules/services/networking/corerad.nix
@@ -14,7 +14,7 @@ in {
 
     settings = mkOption {
       type = settingsFormat.type;
-      example = literalExample ''
+      example = literalExpression ''
         {
           interfaces = [
             # eth0 is an upstream interface monitoring for IPv6 router advertisements.
@@ -44,13 +44,13 @@ in {
 
     configFile = mkOption {
       type = types.path;
-      example = literalExample "\"\${pkgs.corerad}/etc/corerad/corerad.toml\"";
+      example = literalExpression ''"''${pkgs.corerad}/etc/corerad/corerad.toml"'';
       description = "Path to CoreRAD TOML configuration file.";
     };
 
     package = mkOption {
       default = pkgs.corerad;
-      defaultText = literalExample "pkgs.corerad";
+      defaultText = literalExpression "pkgs.corerad";
       type = types.package;
       description = "CoreRAD package to use.";
     };
diff --git a/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix
index 5f7d2893ae27e..610754e9bd39d 100644
--- a/nixos/modules/services/networking/coturn.nix
+++ b/nixos/modules/services/networking/coturn.nix
@@ -68,7 +68,7 @@ in {
       alt-listening-port = mkOption {
         type = types.int;
         default = cfg.listening-port + 1;
-        defaultText = "listening-port + 1";
+        defaultText = literalExpression "listening-port + 1";
         description = ''
           Alternative listening port for UDP and TCP listeners;
           default (or zero) value means "listening port plus one".
@@ -83,7 +83,7 @@ in {
       alt-tls-listening-port = mkOption {
         type = types.int;
         default = cfg.tls-listening-port + 1;
-        defaultText = "tls-listening-port + 1";
+        defaultText = literalExpression "tls-listening-port + 1";
         description = ''
           Alternative listening port for TLS and DTLS protocols.
         '';
@@ -311,6 +311,7 @@ in {
     {
       users.users.turnserver =
         { uid = config.ids.uids.turnserver;
+          group = "turnserver";
           description = "coturn TURN server user";
         };
       users.groups.turnserver =
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
index 7820eedd9327d..fd9c216b06029 100644
--- a/nixos/modules/services/networking/ddclient.nix
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -4,14 +4,16 @@ let
   cfg = config.services.ddclient;
   boolToStr = bool: if bool then "yes" else "no";
   dataDir = "/var/lib/ddclient";
+  StateDirectory = builtins.baseNameOf dataDir;
+  RuntimeDirectory = StateDirectory;
 
-  configText = ''
+  configFile' = pkgs.writeText "ddclient.conf" ''
     # This file can be used as a template for configFile or is automatically generated by Nix options.
     cache=${dataDir}/ddclient.cache
     foreground=YES
     use=${cfg.use}
     login=${cfg.username}
-    password=${cfg.password}
+    password=
     protocol=${cfg.protocol}
     ${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
     ${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
@@ -24,6 +26,17 @@ let
     ${cfg.extraConfig}
     ${lib.concatStringsSep "," cfg.domains}
   '';
+  configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
+
+  preStart = ''
+    install ${configFile} /run/${RuntimeDirectory}/ddclient.conf
+    ${lib.optionalString (cfg.configFile == null) (if (cfg.passwordFile != null) then ''
+      password=$(head -n 1 ${cfg.passwordFile})
+      sed -i "s/^password=$/password=$password/" /run/${RuntimeDirectory}/ddclient.conf
+    '' else ''
+      sed -i '/^password=$/d' /run/${RuntimeDirectory}/ddclient.conf
+    '')}
+  '';
 
 in
 
@@ -37,6 +50,7 @@ with lib;
         let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
         in if value != "" then [ value ] else []))
     (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
+    (mkRemovedOptionModule [ "services" "ddclient" "password" ] "Use services.ddclient.passwordFile instead.")
   ];
 
   ###### interface
@@ -53,6 +67,15 @@ with lib;
         '';
       };
 
+      package = mkOption {
+        type = package;
+        default = pkgs.ddclient;
+        defaultText = "pkgs.ddclient";
+        description = ''
+          The ddclient executable package run by the service.
+        '';
+      };
+
       domains = mkOption {
         default = [ "" ];
         type = listOf str;
@@ -69,11 +92,11 @@ with lib;
         '';
       };
 
-      password = mkOption {
-        default = "";
-        type = str;
+      passwordFile = mkOption {
+        default = null;
+        type = nullOr str;
         description = ''
-          Password. WARNING: The password becomes world readable in the Nix store.
+          A file containing the password.
         '';
       };
 
@@ -87,12 +110,11 @@ with lib;
       };
 
       configFile = mkOption {
-        default = "/etc/ddclient.conf";
-        type = path;
+        default = null;
+        type = nullOr path;
         description = ''
           Path to configuration file.
-          When set to the default '/etc/ddclient.conf' it will be populated with the various other options in this module. When it is changed (for example: '/root/nixos/secrets/ddclient.conf') the file read directly to configure ddclient. This is a source of impurity.
-          The purpose of this is to avoid placing secrets into the store.
+          When set this overrides the generated configuration from module options.
         '';
         example = "/root/nixos/secrets/ddclient.conf";
       };
@@ -184,25 +206,20 @@ with lib;
   ###### implementation
 
   config = mkIf config.services.ddclient.enable {
-    environment.etc."ddclient.conf" = {
-      enable = cfg.configFile == "/etc/ddclient.conf";
-      mode = "0600";
-      text = configText;
-    };
-
     systemd.services.ddclient = {
       description = "Dynamic DNS Client";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      restartTriggers = [ config.environment.etc."ddclient.conf".source ];
+      restartTriggers = optional (cfg.configFile != null) cfg.configFile;
 
-      serviceConfig = rec {
+      serviceConfig = {
         DynamicUser = true;
-        RuntimeDirectory = StateDirectory;
-        StateDirectory = builtins.baseNameOf dataDir;
+        RuntimeDirectoryMode = "0700";
+        inherit RuntimeDirectory;
+        inherit StateDirectory;
         Type = "oneshot";
-        ExecStartPre = "!${lib.getBin pkgs.coreutils}/bin/install -m666 ${cfg.configFile} /run/${RuntimeDirectory}/ddclient.conf";
-        ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
+        ExecStartPre = "!${pkgs.writeShellScript "ddclient-prestart" preStart}";
+        ExecStart = "${lib.getBin cfg.package}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
       };
     };
 
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index 8966deac76cba..54e4f90028598 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -212,9 +212,11 @@ in
 
     users = {
       users.dhcpd = {
-        uid = config.ids.uids.dhcpd;
+        isSystemUser = true;
+        group = "dhcpd";
         description = "DHCP daemon user";
       };
+      groups.dhcpd = {};
     };
 
     systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
diff --git a/nixos/modules/services/networking/dnscache.nix b/nixos/modules/services/networking/dnscache.nix
index d06032daecc72..7452210de47fe 100644
--- a/nixos/modules/services/networking/dnscache.nix
+++ b/nixos/modules/services/networking/dnscache.nix
@@ -61,7 +61,7 @@ in {
           Table of {hostname: server} pairs to use as authoritative servers for hosts (and subhosts).
           If entry for @ is not specified predefined list of root servers is used.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             "@" = ["8.8.8.8" "8.8.4.4"];
             "example.com" = ["192.168.100.100"];
diff --git a/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixos/modules/services/networking/dnscrypt-proxy2.nix
index 72965c267a861..dc6a019e9b776 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy2.nix
+++ b/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -13,7 +13,7 @@ in
         Attrset that is converted and passed as TOML config file.
         For available params, see: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/${pkgs.dnscrypt-proxy2.version}/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
       '';
-      example = literalExample ''
+      example = literalExpression ''
         {
           sources.public-resolvers = {
             urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ];
@@ -29,7 +29,7 @@ in
 
     upstreamDefaults = mkOption {
       description = ''
-        Whether to base the config declared in <literal>services.dnscrypt-proxy2.settings</literal> on the upstream example config (<link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>)
+        Whether to base the config declared in <option>services.dnscrypt-proxy2.settings</option> on the upstream example config (<link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>)
 
         Disable this if you want to declare your dnscrypt config from scratch.
       '';
@@ -56,7 +56,7 @@ in
         ''}
         ${pkgs.remarshal}/bin/json2toml < config.json > $out
       '';
-      defaultText = literalExample "TOML file generated from services.dnscrypt-proxy2.settings";
+      defaultText = literalDocBook "TOML file generated from <option>services.dnscrypt-proxy2.settings</option>";
     };
   };
 
diff --git a/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixos/modules/services/networking/dnscrypt-wrapper.nix
index 89360f4bf3732..400d6e67044e2 100644
--- a/nixos/modules/services/networking/dnscrypt-wrapper.nix
+++ b/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -217,6 +217,7 @@ in {
       home = "${dataDir}";
       createHome = true;
       isSystemUser = true;
+      group = "dnscrypt-wrapper";
     };
     users.groups.dnscrypt-wrapper = { };
 
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 377d7bc570587..59a3ca2f28e37 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -87,9 +87,11 @@ in
     services.dbus.packages = [ dnsmasq ];
 
     users.users.dnsmasq = {
-      uid = config.ids.uids.dnsmasq;
+      isSystemUser = true;
+      group = "dnsmasq";
       description = "Dnsmasq daemon user";
     };
+    users.groups.dnsmasq = {};
 
     networking.resolvconf = mkIf cfg.resolveLocalQueries {
       useLocalResolver = mkDefault true;
diff --git a/nixos/modules/services/networking/doh-proxy-rust.nix b/nixos/modules/services/networking/doh-proxy-rust.nix
index 0e55bc3866536..efd492e23f8c8 100644
--- a/nixos/modules/services/networking/doh-proxy-rust.nix
+++ b/nixos/modules/services/networking/doh-proxy-rust.nix
@@ -15,7 +15,7 @@ in {
     flags = mkOption {
       type = types.listOf types.str;
       default = [];
-      example = literalExample [ "--server-address=9.9.9.9:53" ];
+      example = [ "--server-address=9.9.9.9:53" ];
       description = ''
         A list of command-line flags to pass to doh-proxy. For details on the
         available options, see <link xlink:href="https://github.com/jedisct1/doh-server#usage"/>.
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index a5af25b983b91..daf8d5c424758 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -32,7 +32,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.ejabberd;
-        defaultText = "pkgs.ejabberd";
+        defaultText = literalExpression "pkgs.ejabberd";
         description = "ejabberd server package to use";
       };
 
@@ -76,7 +76,7 @@ in {
         type = types.listOf types.path;
         default = [];
         description = "Configuration dumps that should be loaded on the first startup";
-        example = literalExample "[ ./myejabberd.dump ]";
+        example = literalExpression "[ ./myejabberd.dump ]";
       };
 
       imagemagick = mkOption {
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
index 3899d164f16a7..75d78476e5783 100644
--- a/nixos/modules/services/networking/epmd.nix
+++ b/nixos/modules/services/networking/epmd.nix
@@ -20,6 +20,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.erlang;
+      defaultText = literalExpression "pkgs.erlang";
       description = ''
         The Erlang package to use to get epmd binary. That way you can re-use
         an Erlang runtime that is already installed for other purposes.
diff --git a/nixos/modules/services/networking/ferm.nix b/nixos/modules/services/networking/ferm.nix
index 07338ccf4d9c1..8e03f30efc00a 100644
--- a/nixos/modules/services/networking/ferm.nix
+++ b/nixos/modules/services/networking/ferm.nix
@@ -30,14 +30,14 @@ in {
       config = mkOption {
         description = "Verbatim ferm.conf configuration.";
         default = "";
-        defaultText = "empty firewall, allows any traffic";
+        defaultText = literalDocBook "empty firewall, allows any traffic";
         type = types.lines;
       };
       package = mkOption {
         description = "The ferm package.";
         type = types.package;
         default = pkgs.ferm;
-        defaultText = "pkgs.ferm";
+        defaultText = literalExpression "pkgs.ferm";
       };
     };
   };
diff --git a/nixos/modules/services/networking/firefox/sync-server.nix b/nixos/modules/services/networking/firefox/sync-server.nix
index 24f768649530f..1ad573abfca3c 100644
--- a/nixos/modules/services/networking/firefox/sync-server.nix
+++ b/nixos/modules/services/networking/firefox/sync-server.nix
@@ -119,7 +119,7 @@ in
           password, and the <option>syncserver.secret</option> setting is used by the server to
           generate cryptographically-signed authentication tokens.
 
-          If this file does not exists, then it is created with a generated
+          If this file does not exist, then it is created with a generated
           <option>syncserver.secret</option> settings.
        '';
       };
diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix
index f982621e2328a..b5b46fe6042cd 100644
--- a/nixos/modules/services/networking/firewall.nix
+++ b/nixos/modules/services/networking/firewall.nix
@@ -325,8 +325,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.iptables;
-        defaultText = "pkgs.iptables";
-        example = literalExample "pkgs.iptables-nftables-compat";
+        defaultText = literalExpression "pkgs.iptables";
+        example = literalExpression "pkgs.iptables-nftables-compat";
         description =
           ''
             The iptables package to use for running the firewall service."
@@ -500,7 +500,7 @@ in
       extraPackages = mkOption {
         type = types.listOf types.package;
         default = [ ];
-        example = literalExample "[ pkgs.ipset ]";
+        example = literalExpression "[ pkgs.ipset ]";
         description =
           ''
             Additional packages to be included in the environment of the system
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index 32a7eb3ed69e8..b15339870ee2d 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -20,7 +20,7 @@ in {
       description = "Package to use for flannel";
       type = types.package;
       default = pkgs.flannel;
-      defaultText = "pkgs.flannel";
+      defaultText = literalExpression "pkgs.flannel";
     };
 
     publicIp = mkOption {
@@ -164,7 +164,7 @@ in {
       path = [ pkgs.iptables ];
       preStart = optionalString (cfg.storageBackend == "etcd") ''
         echo "setting network configuration"
-        until ${pkgs.etcdctl}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
+        until ${pkgs.etcd}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
         do
           echo "setting network configuration, retry"
           sleep 1
diff --git a/nixos/modules/services/networking/ghostunnel.nix b/nixos/modules/services/networking/ghostunnel.nix
index 58a51df6cca2a..7a62d378e2c6e 100644
--- a/nixos/modules/services/networking/ghostunnel.nix
+++ b/nixos/modules/services/networking/ghostunnel.nix
@@ -5,7 +5,7 @@ let
     concatMap
     concatStringsSep
     escapeShellArg
-    literalExample
+    literalExpression
     mapAttrs'
     mkDefault
     mkEnableOption
@@ -219,7 +219,7 @@ in
       description = "The ghostunnel package to use.";
       type = types.package;
       default = pkgs.ghostunnel;
-      defaultText = literalExample ''pkgs.ghostunnel'';
+      defaultText = literalExpression "pkgs.ghostunnel";
     };
 
     services.ghostunnel.servers = mkOption {
diff --git a/nixos/modules/services/networking/git-daemon.nix b/nixos/modules/services/networking/git-daemon.nix
index 98f80dd4bc404..6be72505c216e 100644
--- a/nixos/modules/services/networking/git-daemon.nix
+++ b/nixos/modules/services/networking/git-daemon.nix
@@ -107,6 +107,7 @@ in
     users.users = optionalAttrs (cfg.user == "git") {
       git = {
         uid = config.ids.uids.git;
+        group = "git";
         description = "Git daemon user";
       };
     };
diff --git a/nixos/modules/services/networking/globalprotect-vpn.nix b/nixos/modules/services/networking/globalprotect-vpn.nix
index 367a42687e132..976fdf2b962ae 100644
--- a/nixos/modules/services/networking/globalprotect-vpn.nix
+++ b/nixos/modules/services/networking/globalprotect-vpn.nix
@@ -21,7 +21,7 @@ in
         as described at <link xlink:href="https://www.infradead.org/openconnect/hip.html" />
       '';
       default = null;
-      example = literalExample "\${pkgs.openconnect}/libexec/openconnect/hipreport.sh";
+      example = literalExpression ''"''${pkgs.openconnect}/libexec/openconnect/hipreport.sh"'';
       type = types.nullOr types.path;
     };
   };
diff --git a/nixos/modules/services/networking/gnunet.nix b/nixos/modules/services/networking/gnunet.nix
index cf3d1841a9795..5c41967d279b6 100644
--- a/nixos/modules/services/networking/gnunet.nix
+++ b/nixos/modules/services/networking/gnunet.nix
@@ -115,9 +115,9 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.gnunet;
-        defaultText = "pkgs.gnunet";
+        defaultText = literalExpression "pkgs.gnunet";
         description = "Overridable attribute of the gnunet package to use.";
-        example = literalExample "pkgs.gnunet_git";
+        example = literalExpression "pkgs.gnunet_git";
       };
 
       extraOptions = mkOption {
diff --git a/nixos/modules/services/networking/gobgpd.nix b/nixos/modules/services/networking/gobgpd.nix
index d3b03471f4eb5..29ef9a5cf1e3b 100644
--- a/nixos/modules/services/networking/gobgpd.nix
+++ b/nixos/modules/services/networking/gobgpd.nix
@@ -18,7 +18,7 @@ in {
         <link xlink:href="https://github.com/osrg/gobgp#documentation"/>
         for details on supported values.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         {
           global = {
             config = {
diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix
index 84147db00f619..2639b4b680011 100644
--- a/nixos/modules/services/networking/hans.nix
+++ b/nixos/modules/services/networking/hans.nix
@@ -27,7 +27,7 @@ in
           where <replaceable>name</replaceable> is the name of the
           corresponding attribute name.
         '';
-        example = literalExample ''
+        example = literalExpression ''
         {
           foo = {
             server = "192.0.2.1";
diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
index 74960e69b9ac2..8e59c68054d2a 100644
--- a/nixos/modules/services/networking/hylafax/options.nix
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -2,7 +2,7 @@
 
 let
 
-  inherit (lib.options) literalExample mkEnableOption mkOption;
+  inherit (lib.options) literalExpression mkEnableOption mkOption;
   inherit (lib.types) bool enum ints lines attrsOf nullOr path str submodule;
   inherit (lib.modules) mkDefault mkIf mkMerge;
 
@@ -197,7 +197,7 @@ in
 
     sendmailPath = mkOption {
       type = path;
-      example = literalExample "''${pkgs.postfix}/bin/sendmail";
+      example = literalExpression ''"''${pkgs.postfix}/bin/sendmail"'';
       # '' ;  # fix vim
       description = ''
         Path to <filename>sendmail</filename> program.
@@ -344,7 +344,7 @@ in
     faxqclean.doneqMinutes = mkOption {
       type = ints.positive;
       default = 15;
-      example = literalExample "24*60";
+      example = literalExpression "24*60";
       description = ''
         Set the job
         age threshold (in minutes) that controls how long
@@ -354,7 +354,7 @@ in
     faxqclean.docqMinutes = mkOption {
       type = ints.positive;
       default = 60;
-      example = literalExample "24*60";
+      example = literalExpression "24*60";
       description = ''
         Set the document
         age threshold (in minutes) that controls how long
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index fba0d817006ed..17828ca44ff21 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -481,7 +481,7 @@ in
       exploratory.inbound = i2cpOpts "exploratory";
       exploratory.outbound = i2cpOpts "exploratory";
 
-      ntcp2.enable = mkEnableTrueOption "NTCP2.";
+      ntcp2.enable = mkEnableTrueOption "NTCP2";
       ntcp2.published = mkEnableOption "NTCP2 publication";
       ntcp2.port = mkOption {
         type = types.int;
diff --git a/nixos/modules/services/networking/icecream/daemon.nix b/nixos/modules/services/networking/icecream/daemon.nix
index 2975696f9c243..8593c94e34dc3 100644
--- a/nixos/modules/services/networking/icecream/daemon.nix
+++ b/nixos/modules/services/networking/icecream/daemon.nix
@@ -101,7 +101,7 @@ in {
 
       package = mkOption {
         default = pkgs.icecream;
-        defaultText = "pkgs.icecream";
+        defaultText = literalExpression "pkgs.icecream";
         type = types.package;
         description = "Icecream package to use.";
       };
diff --git a/nixos/modules/services/networking/icecream/scheduler.nix b/nixos/modules/services/networking/icecream/scheduler.nix
index 4ccbf27015d7c..14fbc966b9893 100644
--- a/nixos/modules/services/networking/icecream/scheduler.nix
+++ b/nixos/modules/services/networking/icecream/scheduler.nix
@@ -56,7 +56,7 @@ in {
 
       package = mkOption {
         default = pkgs.icecream;
-        defaultText = "pkgs.icecream";
+        defaultText = literalExpression "pkgs.icecream";
         type = types.package;
         description = "Icecream package to use.";
       };
diff --git a/nixos/modules/services/networking/inspircd.nix b/nixos/modules/services/networking/inspircd.nix
index 8cb2b406ee283..81c367ec8f7dd 100644
--- a/nixos/modules/services/networking/inspircd.nix
+++ b/nixos/modules/services/networking/inspircd.nix
@@ -17,8 +17,8 @@ in {
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.inspircd;
-        defaultText = lib.literalExample "pkgs.inspircd";
-        example = lib.literalExample "pkgs.inspircdMinimal";
+        defaultText = lib.literalExpression "pkgs.inspircd";
+        example = lib.literalExpression "pkgs.inspircdMinimal";
         description = ''
           The InspIRCd package to use. This is mainly useful
           to specify an overridden version of the
diff --git a/nixos/modules/services/networking/iodine.nix b/nixos/modules/services/networking/iodine.nix
index 46051d7044b5e..e241afe3269bb 100644
--- a/nixos/modules/services/networking/iodine.nix
+++ b/nixos/modules/services/networking/iodine.nix
@@ -36,7 +36,7 @@ in
           where <replaceable>name</replaceable> is the name of the
           corresponding attribute name.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             foo = {
               server = "tunnel.mdomain.com";
@@ -190,6 +190,7 @@ in
 
     users.users.${iodinedUser} = {
       uid = config.ids.uids.iodined;
+      group = "iodined";
       description = "Iodine daemon user";
     };
     users.groups.iodined.gid = config.ids.gids.iodined;
diff --git a/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixos/modules/services/networking/ircd-hybrid/default.nix
index 1f5636e4e3a9b..f659f3f3e8c13 100644
--- a/nixos/modules/services/networking/ircd-hybrid/default.nix
+++ b/nixos/modules/services/networking/ircd-hybrid/default.nix
@@ -64,7 +64,7 @@ in
 
       rsaKey = mkOption {
         default = null;
-        example = literalExample "/root/certificates/irc.key";
+        example = literalExpression "/root/certificates/irc.key";
         type = types.nullOr types.path;
         description = "
           IRCD server RSA key.
@@ -73,7 +73,7 @@ in
 
       certificate = mkOption {
         default = null;
-        example = literalExample "/root/certificates/irc.pem";
+        example = literalExpression "/root/certificates/irc.pem";
         type = types.nullOr types.path;
         description = "
           IRCD server SSL certificate. There are some limitations - read manual.
diff --git a/nixos/modules/services/networking/iscsi/initiator.nix b/nixos/modules/services/networking/iscsi/initiator.nix
index cbc919a2f76c5..051c9c7bff3c6 100644
--- a/nixos/modules/services/networking/iscsi/initiator.nix
+++ b/nixos/modules/services/networking/iscsi/initiator.nix
@@ -23,7 +23,7 @@ in
       type = package;
       description = "openiscsi package to use";
       default = pkgs.openiscsi;
-      defaultText = "pkgs.openiscsi";
+      defaultText = literalExpression "pkgs.openiscsi";
     };
 
     extraConfig = mkOption {
diff --git a/nixos/modules/services/networking/iscsi/root-initiator.nix b/nixos/modules/services/networking/iscsi/root-initiator.nix
index 3274878c4fae4..c12aca1bc24d7 100644
--- a/nixos/modules/services/networking/iscsi/root-initiator.nix
+++ b/nixos/modules/services/networking/iscsi/root-initiator.nix
@@ -64,6 +64,12 @@ in
       default = false;
     };
 
+    extraIscsiCommands = mkOption {
+      description = "Extra iscsi commands to run in the initrd.";
+      default = "";
+      type = lines;
+    };
+
     extraConfig = mkOption {
       description = "Extra lines to append to /etc/iscsid.conf";
       default = null;
@@ -162,6 +168,9 @@ in
       '' else ''
         iscsiadm --mode node --targetname ${escapeShellArg cfg.target} --login
       ''}
+
+        ${cfg.extraIscsiCommands}
+
         pkill -9 iscsid
       '';
     };
diff --git a/nixos/modules/services/networking/jibri/default.nix b/nixos/modules/services/networking/jibri/default.nix
new file mode 100644
index 0000000000000..96832b0eb552b
--- /dev/null
+++ b/nixos/modules/services/networking/jibri/default.nix
@@ -0,0 +1,417 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jibri;
+
+  # Copied from the jitsi-videobridge.nix file.
+  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;
+
+  defaultJibriConfig = {
+    id = "";
+    single-use-mode = false;
+
+    api = {
+      http.external-api-port = 2222;
+      http.internal-api-port = 3333;
+
+      xmpp.environments = flip mapAttrsToList cfg.xmppEnvironments (name: env: {
+        inherit name;
+
+        xmpp-server-hosts = env.xmppServerHosts;
+        xmpp-domain = env.xmppDomain;
+        control-muc = {
+          domain = env.control.muc.domain;
+          room-name = env.control.muc.roomName;
+          nickname = env.control.muc.nickname;
+        };
+
+        control-login = {
+          domain = env.control.login.domain;
+          username = env.control.login.username;
+          password.__hocon_envvar = toVarName "${name}_control";
+        };
+
+        call-login = {
+          domain = env.call.login.domain;
+          username = env.call.login.username;
+          password.__hocon_envvar = toVarName "${name}_call";
+        };
+
+        strip-from-room-domain = env.stripFromRoomDomain;
+        usage-timeout = env.usageTimeout;
+        trust-all-xmpp-certs = env.disableCertificateVerification;
+      });
+    };
+
+    recording = {
+      recordings-directory = "/tmp/recordings";
+      finalize-script = "${cfg.finalizeScript}";
+    };
+
+    streaming.rtmp-allow-list = [ ".*" ];
+
+    chrome.flags = [
+      "--use-fake-ui-for-media-stream"
+      "--start-maximized"
+      "--kiosk"
+      "--enabled"
+      "--disable-infobars"
+      "--autoplay-policy=no-user-gesture-required"
+    ]
+    ++ lists.optional cfg.ignoreCert
+      "--ignore-certificate-errors";
+
+
+    stats.enable-stats-d = true;
+    webhook.subscribers = [ ];
+
+    jwt-info = { };
+
+    call-status-checks = {
+      no-media-timout = "30 seconds";
+      all-muted-timeout = "10 minutes";
+      default-call-empty-timout = "30 seconds";
+    };
+  };
+  # Allow overriding leaves of the default config despite types.attrs not doing any merging.
+  jibriConfig = recursiveUpdate defaultJibriConfig cfg.config;
+  configFile = pkgs.writeText "jibri.conf" (toHOCON { jibri = jibriConfig; });
+in
+{
+  options.services.jibri = with types; {
+    enable = mkEnableOption "Jitsi BRoadcasting Infrastructure. Currently Jibri must be run on a host that is also running <option>services.jitsi-meet.enable</option>, so for most use cases it will be simpler to run <option>services.jitsi-meet.jibri.enable</option>";
+    config = mkOption {
+      type = attrs;
+      default = { };
+      description = ''
+        Jibri configuration.
+        See <link xlink:href="https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf" />
+        for default configuration with comments.
+      '';
+    };
+
+    finalizeScript = mkOption {
+      type = types.path;
+      default = pkgs.writeScript "finalize_recording.sh" ''
+        #!/bin/sh
+
+        RECORDINGS_DIR=$1
+
+        echo "This is a dummy finalize script" > /tmp/finalize.out
+        echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out
+        echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out
+        echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
+
+        exit 0
+      '';
+      defaultText = literalExpression ''
+        pkgs.writeScript "finalize_recording.sh" ''''''
+        #!/bin/sh
+
+        RECORDINGS_DIR=$1
+
+        echo "This is a dummy finalize script" > /tmp/finalize.out
+        echo "The script was invoked with recordings directory $RECORDINGS_DIR." >> /tmp/finalize.out
+        echo "You should put any finalize logic (renaming, uploading to a service" >> /tmp/finalize.out
+        echo "or storage provider, etc.) in this script" >> /tmp/finalize.out
+
+        exit 0
+        '''''';
+      '';
+      example = literalExpression ''
+        pkgs.writeScript "finalize_recording.sh" ''''''
+        #!/bin/sh
+        RECORDINGS_DIR=$1
+        ${pkgs.rclone}/bin/rclone copy $RECORDINGS_DIR RCLONE_REMOTE:jibri-recordings/ -v --log-file=/var/log/jitsi/jibri/recording-upload.txt
+        exit 0
+        '''''';
+      '';
+      description = ''
+        This script runs when jibri finishes recording a video of a conference.
+      '';
+    };
+
+    ignoreCert = mkOption {
+      type = bool;
+      default = false;
+      example = true;
+      description = ''
+        Whether to enable the flag "--ignore-certificate-errors" for the Chromium browser opened by Jibri.
+        Intended for use in automated tests or anywhere else where using a verified cert for Jitsi-Meet is not possible.
+      '';
+    };
+
+    xmppEnvironments = mkOption {
+      description = ''
+        XMPP servers to connect to.
+      '';
+      example = literalExpression ''
+        "jitsi-meet" = {
+          xmppServerHosts = [ "localhost" ];
+          xmppDomain = config.services.jitsi-meet.hostName;
+
+          control.muc = {
+            domain = "internal.''${config.services.jitsi-meet.hostName}";
+            roomName = "JibriBrewery";
+            nickname = "jibri";
+          };
+
+          control.login = {
+            domain = "auth.''${config.services.jitsi-meet.hostName}";
+            username = "jibri";
+            passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
+          };
+
+          call.login = {
+            domain = "recorder.''${config.services.jitsi-meet.hostName}";
+            username = "recorder";
+            passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
+          };
+
+          usageTimeout = "0";
+          disableCertificateVerification = true;
+          stripFromRoomDomain = "conference.";
+        };
+      '';
+      default = { };
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          xmppServerHosts = mkOption {
+            type = listOf str;
+            example = [ "xmpp.example.org" ];
+            description = ''
+              Hostnames of the XMPP servers to connect to.
+            '';
+          };
+          xmppDomain = mkOption {
+            type = str;
+            example = "xmpp.example.org";
+            description = ''
+              The base XMPP domain.
+            '';
+          };
+          control.muc.domain = mkOption {
+            type = str;
+            description = ''
+              The domain part of the MUC to connect to for control.
+            '';
+          };
+          control.muc.roomName = mkOption {
+            type = str;
+            default = "JibriBrewery";
+            description = ''
+              The room name of the MUC to connect to for control.
+            '';
+          };
+          control.muc.nickname = mkOption {
+            type = str;
+            default = "jibri";
+            description = ''
+              The nickname for this Jibri instance in the MUC.
+            '';
+          };
+          control.login.domain = mkOption {
+            type = str;
+            description = ''
+              The domain part of the JID for this Jibri instance.
+            '';
+          };
+          control.login.username = mkOption {
+            type = str;
+            default = "jvb";
+            description = ''
+              User part of the JID.
+            '';
+          };
+          control.login.passwordFile = mkOption {
+            type = str;
+            example = "/run/keys/jibri-xmpp1";
+            description = ''
+              File containing the password for the user.
+            '';
+          };
+
+          call.login.domain = mkOption {
+            type = str;
+            example = "recorder.xmpp.example.org";
+            description = ''
+              The domain part of the JID for the recorder.
+            '';
+          };
+          call.login.username = mkOption {
+            type = str;
+            default = "recorder";
+            description = ''
+              User part of the JID for the recorder.
+            '';
+          };
+          call.login.passwordFile = mkOption {
+            type = str;
+            example = "/run/keys/jibri-recorder-xmpp1";
+            description = ''
+              File containing the password for the user.
+            '';
+          };
+          disableCertificateVerification = mkOption {
+            type = bool;
+            default = false;
+            description = ''
+              Whether to skip validation of the server's certificate.
+            '';
+          };
+
+          stripFromRoomDomain = mkOption {
+            type = str;
+            default = "0";
+            example = "conference.";
+            description = ''
+              The prefix to strip from the room's JID domain to derive the call URL.
+            '';
+          };
+          usageTimeout = mkOption {
+            type = str;
+            default = "0";
+            example = "1 hour";
+            description = ''
+              The duration that the Jibri session can be.
+              A value of zero means indefinitely.
+            '';
+          };
+        };
+
+        config =
+          let
+            nick = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
+              config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
+            ));
+          in
+          {
+            call.login.username = nick;
+            control.muc.nickname = nick;
+          };
+      }));
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups.jibri = { };
+    users.groups.plugdev = { };
+    users.users.jibri = {
+      isSystemUser = true;
+      group = "jibri";
+      home = "/var/lib/jibri";
+      extraGroups = [ "jitsi-meet" "adm" "audio" "video" "plugdev" ];
+    };
+
+    systemd.services.jibri-xorg = {
+      description = "Jitsi Xorg Process";
+
+      after = [ "network.target" ];
+      wantedBy = [ "jibri.service" "jibri-icewm.service" ];
+
+      preStart = ''
+        cp --no-preserve=mode,ownership ${pkgs.jibri}/etc/jitsi/jibri/* /var/lib/jibri
+        mv /var/lib/jibri/{,.}asoundrc
+      '';
+
+      environment.DISPLAY = ":0";
+      serviceConfig = {
+        Type = "simple";
+
+        User = "jibri";
+        Group = "jibri";
+        KillMode = "process";
+        Restart = "on-failure";
+        RestartPreventExitStatus = 255;
+
+        StateDirectory = "jibri";
+
+        ExecStart = "${pkgs.xorg.xorgserver}/bin/Xorg -nocursor -noreset +extension RANDR +extension RENDER -config ${pkgs.jibri}/etc/jitsi/jibri/xorg-video-dummy.conf -logfile /dev/null :0";
+      };
+    };
+
+    systemd.services.jibri-icewm = {
+      description = "Jitsi Window Manager";
+
+      requires = [ "jibri-xorg.service" ];
+      after = [ "jibri-xorg.service" ];
+      wantedBy = [ "jibri.service" ];
+
+      environment.DISPLAY = ":0";
+      serviceConfig = {
+        Type = "simple";
+
+        User = "jibri";
+        Group = "jibri";
+        Restart = "on-failure";
+        RestartPreventExitStatus = 255;
+
+        StateDirectory = "jibri";
+
+        ExecStart = "${pkgs.icewm}/bin/icewm-session";
+      };
+    };
+
+    systemd.services.jibri = {
+      description = "Jibri Process";
+
+      requires = [ "jibri-icewm.service" "jibri-xorg.service" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = with pkgs; [ chromedriver chromium ffmpeg-full ];
+
+      script = (concatStrings (mapAttrsToList
+        (name: env: ''
+          export ${toVarName "${name}_control"}=$(cat ${env.control.login.passwordFile})
+          export ${toVarName "${name}_call"}=$(cat ${env.call.login.passwordFile})
+        '')
+        cfg.xmppEnvironments))
+      + ''
+        ${pkgs.jre8_headless}/bin/java -Djava.util.logging.config.file=${./logging.properties-journal} -Dconfig.file=${configFile} -jar ${pkgs.jibri}/opt/jitsi/jibri/jibri.jar --config /var/lib/jibri/jibri.json
+      '';
+
+      environment.HOME = "/var/lib/jibri";
+
+      serviceConfig = {
+        Type = "simple";
+
+        User = "jibri";
+        Group = "jibri";
+        Restart = "always";
+        RestartPreventExitStatus = 255;
+
+        StateDirectory = "jibri";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /var/log/jitsi/jibri 755 jibri jibri"
+    ];
+
+
+
+    # Configure Chromium to not show the "Chrome is being controlled by automatic test software" message.
+    environment.etc."chromium/policies/managed/managed_policies.json".text = builtins.toJSON { CommandLineFlagSecurityWarningsEnabled = false; };
+    warnings = [ "All security warnings for Chromium have been disabled. This is necessary for Jibri, but it also impacts all other uses of Chromium on this system." ];
+
+    boot = {
+      extraModprobeConfig = ''
+        options snd-aloop enable=1,1,1,1,1,1,1,1
+      '';
+      kernelModules = [ "snd-aloop" ];
+    };
+  };
+
+  meta.maintainers = lib.teams.jitsi.members;
+}
diff --git a/nixos/modules/services/networking/jibri/logging.properties-journal b/nixos/modules/services/networking/jibri/logging.properties-journal
new file mode 100644
index 0000000000000..61eadbfddcb36
--- /dev/null
+++ b/nixos/modules/services/networking/jibri/logging.properties-journal
@@ -0,0 +1,32 @@
+handlers = java.util.logging.FileHandler
+
+java.util.logging.FileHandler.level = FINE
+java.util.logging.FileHandler.pattern   = /var/log/jitsi/jibri/log.%g.txt
+java.util.logging.FileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
+java.util.logging.FileHandler.count = 10
+java.util.logging.FileHandler.limit = 10000000
+
+org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.level = FINE
+org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.pattern   = /var/log/jitsi/jibri/ffmpeg.%g.txt
+org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
+org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.count = 10
+org.jitsi.jibri.capture.ffmpeg.util.FfmpegFileHandler.limit = 10000000
+
+org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.level = FINE
+org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.pattern   = /var/log/jitsi/jibri/pjsua.%g.txt
+org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
+org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.count = 10
+org.jitsi.jibri.sipgateway.pjsua.util.PjsuaFileHandler.limit = 10000000
+
+org.jitsi.jibri.selenium.util.BrowserFileHandler.level = FINE
+org.jitsi.jibri.selenium.util.BrowserFileHandler.pattern   = /var/log/jitsi/jibri/browser.%g.txt
+org.jitsi.jibri.selenium.util.BrowserFileHandler.formatter = net.java.sip.communicator.util.ScLogFormatter
+org.jitsi.jibri.selenium.util.BrowserFileHandler.count = 10
+org.jitsi.jibri.selenium.util.BrowserFileHandler.limit = 10000000
+
+org.jitsi.level = FINE
+org.jitsi.jibri.config.level = INFO
+
+org.glassfish.level = INFO
+org.osgi.level = INFO
+org.jitsi.xmpp.level = INFO
diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix
index 160a5fea91a0e..647119b9039e6 100644
--- a/nixos/modules/services/networking/jicofo.nix
+++ b/nixos/modules/services/networking/jicofo.nix
@@ -70,7 +70,7 @@ in
     config = mkOption {
       type = attrsOf str;
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
         }
diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix
index 80f35d56e2dbf..dd06ad98a9730 100644
--- a/nixos/modules/services/networking/jitsi-videobridge.nix
+++ b/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -56,7 +56,7 @@ in
     config = mkOption {
       type = attrs;
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           videobridge = {
             ice.udp.port = 5000;
@@ -82,7 +82,7 @@ in
         See <link xlink:href="https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md" /> for more information.
       '';
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           "localhost" = {
             hostName = "localhost";
@@ -199,7 +199,7 @@ in
         Needed for monitoring jitsi.
       '';
       default = [];
-      example = literalExample "[ \"colibri\" \"rest\" ]";
+      example = literalExpression "[ \"colibri\" \"rest\" ]";
     };
   };
 
diff --git a/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix b/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
index 85b9bc3377268..e96dde5fa89f6 100644
--- a/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
+++ b/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
@@ -102,9 +102,7 @@ with lib;
         inherit lib;
       }));
       default = [];
-      example = literalExample ''
-        TODO: Example
-      '';
+      # TODO: example
       description = "Declarative vhost config";
     };
 
diff --git a/nixos/modules/services/networking/keepalived/vrrp-script-options.nix b/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
index a3f794c40a89a..df7a89cff8cdd 100644
--- a/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
+++ b/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
@@ -7,7 +7,7 @@ with lib.types;
 
     script = mkOption {
       type = str;
-      example = "\${pkgs.curl} -f http://localhost:80";
+      example = literalExpression ''"''${pkgs.curl} -f http://localhost:80"'';
       description = "(Path of) Script command to execute followed by args, i.e. cmd [args]...";
     };
 
diff --git a/nixos/modules/services/networking/kippo.nix b/nixos/modules/services/networking/kippo.nix
deleted file mode 100644
index 6fedb0a270f4c..0000000000000
--- a/nixos/modules/services/networking/kippo.nix
+++ /dev/null
@@ -1,117 +0,0 @@
-# NixOS module for kippo honeypot ssh server
-# See all the options for configuration details.
-#
-# Default port is 2222. Recommend using something like this for port redirection to default SSH port:
-# networking.firewall.extraCommands = ''
-#      iptables -t nat -A PREROUTING -i IN_IFACE -p tcp --dport 22 -j REDIRECT --to-port 2222'';
-#
-# Lastly: use this service at your own risk. I am working on a way to run this inside a VM.
-{ config, lib, pkgs, ... }:
-with lib;
-let
-  cfg = config.services.kippo;
-in
-{
-  options = {
-    services.kippo = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Enable the kippo honeypot ssh server.";
-      };
-      port = mkOption {
-        default = 2222;
-        type = types.int;
-        description = "TCP port number for kippo to bind to.";
-      };
-      hostname = mkOption {
-        default = "nas3";
-        type = types.str;
-        description = "Hostname for kippo to present to SSH login";
-      };
-      varPath = mkOption {
-        default = "/var/lib/kippo";
-        type = types.path;
-        description = "Path of read/write files needed for operation and configuration.";
-      };
-      logPath = mkOption {
-        default = "/var/log/kippo";
-        type = types.path;
-        description = "Path of log files needed for operation and configuration.";
-      };
-      pidPath = mkOption {
-        default = "/run/kippo";
-        type = types.path;
-        description = "Path of pid files needed for operation.";
-      };
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        description = "Extra verbatim configuration added to the end of kippo.cfg.";
-      };
-    };
-
-  };
-  config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs.pythonPackages; [
-      python pkgs.kippo.twisted pycrypto pyasn1 ];
-
-    environment.etc."kippo.cfg".text = ''
-        # Automatically generated by NixOS.
-        # See ${pkgs.kippo}/src/kippo.cfg for details.
-        [honeypot]
-        log_path = ${cfg.logPath}
-        download_path = ${cfg.logPath}/dl
-        filesystem_file = ${cfg.varPath}/honeyfs
-        filesystem_file = ${cfg.varPath}/fs.pickle
-        data_path = ${cfg.varPath}/data
-        txtcmds_path = ${cfg.varPath}/txtcmds
-        public_key = ${cfg.varPath}/keys/public.key
-        private_key = ${cfg.varPath}/keys/private.key
-        ssh_port = ${toString cfg.port}
-        hostname = ${cfg.hostname}
-        ${cfg.extraConfig}
-    '';
-
-    users.users.kippo = {
-      description = "kippo web server privilege separation user";
-      uid = 108; # why does config.ids.uids.kippo give an error?
-    };
-    users.groups.kippo.gid = 108;
-
-    systemd.services.kippo = with pkgs; {
-      description = "Kippo Web Server";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      environment.PYTHONPATH = "${pkgs.kippo}/src/:${pkgs.pythonPackages.pycrypto}/lib/python2.7/site-packages/:${pkgs.pythonPackages.pyasn1}/lib/python2.7/site-packages/:${pkgs.pythonPackages.python}/lib/python2.7/site-packages/:${pkgs.kippo.twisted}/lib/python2.7/site-packages/:.";
-      preStart = ''
-        if [ ! -d ${cfg.varPath}/ ] ; then
-            mkdir -p ${cfg.logPath}/tty
-            mkdir -p ${cfg.logPath}/dl
-            mkdir -p ${cfg.varPath}/keys
-            cp ${pkgs.kippo}/src/honeyfs ${cfg.varPath} -r
-            cp ${pkgs.kippo}/src/fs.pickle ${cfg.varPath}/fs.pickle
-            cp ${pkgs.kippo}/src/data ${cfg.varPath} -r
-            cp ${pkgs.kippo}/src/txtcmds ${cfg.varPath} -r
-
-            chmod u+rw ${cfg.varPath} -R
-            chown kippo.kippo ${cfg.varPath} -R
-            chown kippo.kippo ${cfg.logPath} -R
-            chmod u+rw ${cfg.logPath} -R
-        fi
-        if [ ! -d ${cfg.pidPath}/ ] ; then
-            mkdir -p ${cfg.pidPath}
-            chmod u+rw ${cfg.pidPath}
-            chown kippo.kippo ${cfg.pidPath}
-        fi
-      '';
-
-      serviceConfig.ExecStart = "${pkgs.kippo.twisted}/bin/twistd -y ${pkgs.kippo}/src/kippo.tac --syslog --rundir=${cfg.varPath}/ --pidfile=${cfg.pidPath}/kippo.pid --prefix=kippo -n";
-      serviceConfig.PermissionsStartOnly = true;
-      serviceConfig.User = "kippo";
-      serviceConfig.Group = "kippo";
-    };
-};
-}
-
-
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
index 12ff89fe84923..67eadbd767024 100644
--- a/nixos/modules/services/networking/knot.nix
+++ b/nixos/modules/services/networking/knot.nix
@@ -71,7 +71,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.knot-dns;
-        defaultText = "pkgs.knot-dns";
+        defaultText = literalExpression "pkgs.knot-dns";
         description = ''
           Which Knot DNS package to use
         '';
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index 6882a315f616d..3a36ac7e6670e 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -62,8 +62,8 @@ in {
         knot-resolver package to use.
       ";
       default = pkgs.knot-resolver;
-      defaultText = "pkgs.knot-resolver";
-      example = literalExample "pkgs.knot-resolver.override { extraFeatures = true; }";
+      defaultText = literalExpression "pkgs.knot-resolver";
+      example = literalExpression "pkgs.knot-resolver.override { extraFeatures = true; }";
     };
     extraConfig = mkOption {
       type = types.lines;
diff --git a/nixos/modules/services/networking/lambdabot.nix b/nixos/modules/services/networking/lambdabot.nix
index b7c8bd008fe1c..3005e58245546 100644
--- a/nixos/modules/services/networking/lambdabot.nix
+++ b/nixos/modules/services/networking/lambdabot.nix
@@ -27,7 +27,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.lambdabot;
-        defaultText = "pkgs.lambdabot";
+        defaultText = literalExpression "pkgs.lambdabot";
         description = "Used lambdabot package";
       };
 
diff --git a/nixos/modules/services/networking/libreswan.nix b/nixos/modules/services/networking/libreswan.nix
index 1f0423ac3d843..429167aed9d7e 100644
--- a/nixos/modules/services/networking/libreswan.nix
+++ b/nixos/modules/services/networking/libreswan.nix
@@ -66,7 +66,7 @@ in
       connections = mkOption {
         type = types.attrsOf types.lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           { myconnection = '''
               auto=add
               left=%defaultroute
@@ -85,7 +85,7 @@ in
       policies = mkOption {
         type = types.attrsOf types.lines;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           { private-or-clear = '''
               # Attempt opportunistic IPsec for the entire Internet
               0.0.0.0/0
diff --git a/nixos/modules/services/networking/lxd-image-server.nix b/nixos/modules/services/networking/lxd-image-server.nix
new file mode 100644
index 0000000000000..5ec6cacffa497
--- /dev/null
+++ b/nixos/modules/services/networking/lxd-image-server.nix
@@ -0,0 +1,138 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lxd-image-server;
+  format = pkgs.formats.toml {};
+
+  location = "/var/www/simplestreams";
+in
+{
+  options = {
+    services.lxd-image-server = {
+      enable = mkEnableOption "lxd-image-server";
+
+      group = mkOption {
+        type = types.str;
+        description = "Group assigned to the user and the webroot directory.";
+        default = "nginx";
+        example = "www-data";
+      };
+
+      settings = mkOption {
+        type = format.type;
+        description = ''
+          Configuration for lxd-image-server.
+
+          Example see <link xlink:href="https://github.com/Avature/lxd-image-server/blob/master/config.toml"/>.
+        '';
+        default = {};
+      };
+
+      nginx = {
+        enable = mkEnableOption "nginx";
+        domain = mkOption {
+          type = types.str;
+          description = "Domain to use for nginx virtual host.";
+          example = "images.example.org";
+        };
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf (cfg.enable) {
+      users.users.lxd-image-server = {
+        isSystemUser = true;
+        group = cfg.group;
+      };
+      users.groups.${cfg.group} = {};
+
+      environment.etc."lxd-image-server/config.toml".source = format.generate "config.toml" cfg.settings;
+
+      services.logrotate.paths.lxd-image-server = {
+        path = "/var/log/lxd-image-server/lxd-image-server.log";
+        frequency = "daily";
+        keep = 21;
+        user = "lxd-image-server";
+        group = cfg.group;
+        extraConfig = ''
+          missingok
+          compress
+          delaycompress
+          copytruncate
+          notifempty
+        '';
+      };
+
+      systemd.tmpfiles.rules = [
+        "d /var/www/simplestreams 0755 lxd-image-server ${cfg.group}"
+      ];
+
+      systemd.services.lxd-image-server = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        description = "LXD Image Server";
+
+        script = ''
+          ${pkgs.lxd-image-server}/bin/lxd-image-server init
+          ${pkgs.lxd-image-server}/bin/lxd-image-server watch
+        '';
+
+        serviceConfig = {
+          User = "lxd-image-server";
+          Group = cfg.group;
+          DynamicUser = true;
+          LogsDirectory = "lxd-image-server";
+          RuntimeDirectory = "lxd-image-server";
+          ExecReload = "${pkgs.lxd-image-server}/bin/lxd-image-server reload";
+          ReadWritePaths = [ location ];
+        };
+      };
+    })
+    # this is seperate so it can be enabled on mirrored hosts
+    (mkIf (cfg.nginx.enable) {
+      # https://github.com/Avature/lxd-image-server/blob/master/resources/nginx/includes/lxd-image-server.pkg.conf
+      services.nginx.virtualHosts = {
+        "${cfg.nginx.domain}" = {
+          forceSSL = true;
+          enableACME = mkDefault true;
+
+          root = location;
+
+          locations = {
+            "/streams/v1/" = {
+              index = "index.json";
+            };
+
+            # Serve json files with content type header application/json
+            "~ \.json$" = {
+              extraConfig = ''
+                add_header Content-Type application/json;
+              '';
+            };
+
+            "~ \.tar.xz$" = {
+              extraConfig = ''
+                add_header Content-Type application/octet-stream;
+              '';
+            };
+
+            "~ \.tar.gz$" = {
+              extraConfig = ''
+                add_header Content-Type application/octet-stream;
+              '';
+            };
+
+            # Deny access to document root and the images folder
+            "~ ^/(images/)?$" = {
+              return = "403";
+            };
+          };
+        };
+      };
+    })
+  ];
+}
diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix
index c580ba47dad33..c860f63efa673 100644
--- a/nixos/modules/services/networking/minidlna.nix
+++ b/nixos/modules/services/networking/minidlna.nix
@@ -39,7 +39,7 @@ in
     services.minidlna.friendlyName = mkOption {
       type = types.str;
       default = "${config.networking.hostName} MiniDLNA";
-      defaultText = "$HOSTNAME MiniDLNA";
+      defaultText = literalExpression ''"''${config.networking.hostName} MiniDLNA"'';
       example = "rpi3";
       description =
         ''
diff --git a/nixos/modules/services/networking/miredo.nix b/nixos/modules/services/networking/miredo.nix
index 2c8393fb5b41f..b7f657efb712c 100644
--- a/nixos/modules/services/networking/miredo.nix
+++ b/nixos/modules/services/networking/miredo.nix
@@ -25,7 +25,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.miredo;
-        defaultText = "pkgs.miredo";
+        defaultText = literalExpression "pkgs.miredo";
         description = ''
           The package to use for the miredo daemon's binary.
         '';
diff --git a/nixos/modules/services/networking/morty.nix b/nixos/modules/services/networking/morty.nix
index e110a5c86101f..dff2f482ca6b2 100644
--- a/nixos/modules/services/networking/morty.nix
+++ b/nixos/modules/services/networking/morty.nix
@@ -23,7 +23,6 @@ in
         type = types.bool;
         default = true;
         description = "Allow IPv6 HTTP requests?";
-        defaultText = "Allow IPv6 HTTP requests.";
       };
 
       key = mkOption {
@@ -33,21 +32,20 @@ in
           HMAC url validation key (hexadecimal encoded).
           Leave blank to disable. Without validation key, anyone can
           submit proxy requests. Leave blank to disable.
+          Generate with <literal>printf %s somevalue | openssl dgst -sha1 -hmac somekey</literal>
         '';
-        defaultText = "No HMAC url validation. Generate with echo -n somevalue | openssl dgst -sha1 -hmac somekey";
       };
 
       timeout = mkOption {
         type = types.int;
         default = 2;
         description = "Request timeout in seconds.";
-        defaultText = "A resource now gets 2 seconds to respond.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.morty;
-        defaultText = "pkgs.morty";
+        defaultText = literalExpression "pkgs.morty";
         description = "morty package to use.";
       };
 
@@ -61,7 +59,6 @@ in
         type = types.str;
         default = "127.0.0.1";
         description = "The address on which the service listens";
-        defaultText = "127.0.0.1 (localhost)";
       };
 
     };
@@ -77,7 +74,9 @@ in
         createHome = true;
         home = "/var/lib/morty";
         isSystemUser = true;
+        group = "morty";
       };
+    users.groups.morty = {};
 
     systemd.services.morty =
       {
diff --git a/nixos/modules/services/networking/mosquitto.md b/nixos/modules/services/networking/mosquitto.md
new file mode 100644
index 0000000000000..5cdb598151e51
--- /dev/null
+++ b/nixos/modules/services/networking/mosquitto.md
@@ -0,0 +1,102 @@
+# Mosquitto {#module-services-mosquitto}
+
+Mosquitto is a MQTT broker often used for IoT or home automation data transport.
+
+## Quickstart {#module-services-mosquitto-quickstart}
+
+A minimal configuration for Mosquitto is
+
+```nix
+services.mosquitto = {
+  enable = true;
+  listeners = [ {
+    acl = [ "pattern readwrite #" ];
+    omitPasswordAuth = true;
+    settings.allow_anonymous = true;
+  } ];
+};
+```
+
+This will start a broker on port 1883, listening on all interfaces of the machine, allowing
+read/write access to all topics to any user without password requirements.
+
+User authentication can be configured with the `users` key of listeners. A config that gives
+full read access to a user `monitor` and restricted write access to a user `service` could look
+like
+
+```nix
+services.mosquitto = {
+  enable = true;
+  listeners = [ {
+    users = {
+      monitor = {
+        acl = [ "read #" ];
+        password = "monitor";
+      };
+      service = {
+        acl = [ "write service/#" ];
+        password = "service";
+      };
+    };
+  } ];
+};
+```
+
+TLS authentication is configured by setting TLS-related options of the listener:
+
+```nix
+services.mosquitto = {
+  enable = true;
+  listeners = [ {
+    port = 8883; # port change is not required, but helpful to avoid mistakes
+    # ...
+    settings = {
+      cafile = "/path/to/mqtt.ca.pem";
+      certfile = "/path/to/mqtt.pem";
+      keyfile = "/path/to/mqtt.key";
+    };
+  } ];
+```
+
+## Configuration {#module-services-mosquitto-config}
+
+The Mosquitto configuration has four distinct types of settings:
+the global settings of the daemon, listeners, plugins, and bridges.
+Bridges and listeners are part of the global configuration, plugins are part of listeners.
+Users of the broker are configured as parts of listeners rather than globally, allowing
+configurations in which a given user is only allowed to log in to the broker using specific
+listeners (eg to configure an admin user with full access to all topics, but restricted to
+localhost).
+
+Almost all options of Mosquitto are available for configuration at their appropriate levels, some
+as NixOS options written in camel case, the remainders under `settings` with their exact names in
+the Mosquitto config file. The exceptions are `acl_file` (which is always set according to the
+`acl` attributes of a listener and its users) and `per_listener_settings` (which is always set to
+`true`).
+
+### Password authentication {#module-services-mosquitto-config-passwords}
+
+Mosquitto can be run in two modes, with a password file or without. Each listener has its own
+password file, and different listeners may use different password files. Password file generation
+can be disabled by setting `omitPasswordAuth = true` for a listener; in this case it is necessary
+to either set `settings.allow_anonymous = true` to allow all logins, or to configure other
+authentication methods like TLS client certificates with `settings.use_identity_as_username = true`.
+
+The default is to generate a password file for each listener from the users configured to that
+listener. Users with no configured password will not be added to the password file and thus
+will not be able to use the broker.
+
+### ACL format {#module-services-mosquitto-config-acl}
+
+Every listener has a Mosquitto `acl_file` attached to it. This ACL is configured via two
+attributes of the config:
+
+  * the `acl` attribute of the listener configures pattern ACL entries and topic ACL entries
+    for anonymous users. Each entry must be prefixed with `pattern` or `topic` to distinguish
+    between these two cases.
+  * the `acl` attribute of every user configures in the listener configured the ACL for that
+    given user. Only topic ACLs are supported by Mosquitto in this setting, so no prefix is
+    required or allowed.
+
+The default ACL for a listener is empty, disallowing all accesses from all clients. To configure
+a completely open ACL, set `acl = [ "pattern readwrite #" ]` in the listener.
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index 8e814ffd0b9b0..2d498d4dbbcf5 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -5,217 +5,553 @@ with lib;
 let
   cfg = config.services.mosquitto;
 
-  listenerConf = optionalString cfg.ssl.enable ''
-    listener ${toString cfg.ssl.port} ${cfg.ssl.host}
-    cafile ${cfg.ssl.cafile}
-    certfile ${cfg.ssl.certfile}
-    keyfile ${cfg.ssl.keyfile}
-  '';
-
-  passwordConf = optionalString cfg.checkPasswords ''
-    password_file ${cfg.dataDir}/passwd
-  '';
-
-  mosquittoConf = pkgs.writeText "mosquitto.conf" ''
-    acl_file ${aclFile}
-    persistence true
-    allow_anonymous ${boolToString cfg.allowAnonymous}
-    listener ${toString cfg.port} ${cfg.host}
-    ${passwordConf}
-    ${listenerConf}
-    ${cfg.extraConf}
-  '';
-
-  userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
-    "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
-  ));
-
-  aclFile = pkgs.writeText "mosquitto.acl" ''
-    ${cfg.aclExtraConf}
-    ${userAcl}
-  '';
+  # note that mosquitto config parsing is very simplistic as of may 2021.
+  # often times they'll e.g. strtok() a line, check the first two tokens, and ignore the rest.
+  # there's no escaping available either, so we have to prevent any being necessary.
+  str = types.strMatching "[^\r\n]*" // {
+    description = "single-line string";
+  };
+  path = types.addCheck types.path (p: str.check "${p}");
+  configKey = types.strMatching "[^\r\n\t ]+";
+  optionType = with types; oneOf [ str path bool int ] // {
+    description = "string, path, bool, or integer";
+  };
+  optionToString = v:
+    if isBool v then boolToString v
+    else if path.check v then "${v}"
+    else toString v;
+
+  assertKeysValid = prefix: valid: config:
+    mapAttrsToList
+      (n: _: {
+        assertion = valid ? ${n};
+        message = "Invalid config key ${prefix}.${n}.";
+      })
+      config;
+
+  formatFreeform = { prefix ? "" }: mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}");
+
+  userOptions = with types; submodule {
+    options = {
+      password = mkOption {
+        type = uniq (nullOr str);
+        default = null;
+        description = ''
+          Specifies the (clear text) password for the MQTT User.
+        '';
+      };
 
-in
+      passwordFile = mkOption {
+        type = uniq (nullOr types.path);
+        example = "/path/to/file";
+        default = null;
+        description = ''
+          Specifies the path to a file containing the
+          clear text password for the MQTT user.
+        '';
+      };
 
-{
+      hashedPassword = mkOption {
+        type = uniq (nullOr str);
+        default = null;
+        description = ''
+          Specifies the hashed password for the MQTT User.
+          To generate hashed password install <literal>mosquitto</literal>
+          package and use <literal>mosquitto_passwd</literal>.
+        '';
+      };
 
-  ###### Interface
+      hashedPasswordFile = mkOption {
+        type = uniq (nullOr types.path);
+        example = "/path/to/file";
+        default = null;
+        description = ''
+          Specifies the path to a file containing the
+          hashed password for the MQTT user.
+          To generate hashed password install <literal>mosquitto</literal>
+          package and use <literal>mosquitto_passwd</literal>.
+        '';
+      };
 
-  options = {
-    services.mosquitto = {
-      enable = mkEnableOption "the MQTT Mosquitto broker";
+      acl = mkOption {
+        type = listOf str;
+        example = [ "read A/B" "readwrite A/#" ];
+        default = [];
+        description = ''
+          Control client access to topics on the broker.
+        '';
+      };
+    };
+  };
 
-      host = mkOption {
-        default = "127.0.0.1";
-        example = "0.0.0.0";
-        type = types.str;
+  userAsserts = prefix: users:
+    mapAttrsToList
+      (n: _: {
+        assertion = builtins.match "[^:\r\n]+" n != null;
+        message = "Invalid user name ${n} in ${prefix}";
+      })
+      users
+    ++ mapAttrsToList
+      (n: u: {
+        assertion = count (s: s != null) [
+          u.password u.passwordFile u.hashedPassword u.hashedPasswordFile
+        ] <= 1;
+        message = "Cannot set more than one password option for user ${n} in ${prefix}";
+      }) users;
+
+  makePasswordFile = users: path:
+    let
+      makeLines = store: file:
+        mapAttrsToList
+          (n: u: "addLine ${escapeShellArg n} ${escapeShellArg u.${store}}")
+          (filterAttrs (_: u: u.${store} != null) users)
+        ++ mapAttrsToList
+          (n: u: "addFile ${escapeShellArg n} ${escapeShellArg "${u.${file}}"}")
+          (filterAttrs (_: u: u.${file} != null) users);
+      plainLines = makeLines "password" "passwordFile";
+      hashedLines = makeLines "hashedPassword" "hashedPasswordFile";
+    in
+      pkgs.writeScript "make-mosquitto-passwd"
+        (''
+          #! ${pkgs.runtimeShell}
+
+          set -eu
+
+          file=${escapeShellArg path}
+
+          rm -f "$file"
+          touch "$file"
+
+          addLine() {
+            echo "$1:$2" >> "$file"
+          }
+          addFile() {
+            if [ $(wc -l <"$2") -gt 1 ]; then
+              echo "invalid mosquitto password file $2" >&2
+              return 1
+            fi
+            echo "$1:$(cat "$2")" >> "$file"
+          }
+        ''
+        + concatStringsSep "\n"
+          (plainLines
+           ++ optional (plainLines != []) ''
+             ${pkgs.mosquitto}/bin/mosquitto_passwd -U "$file"
+           ''
+           ++ hashedLines));
+
+  makeACLFile = idx: users: supplement:
+    pkgs.writeText "mosquitto-acl-${toString idx}.conf"
+      (concatStringsSep
+        "\n"
+        (flatten [
+          supplement
+          (mapAttrsToList
+            (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl)
+            users)
+        ]));
+
+  authPluginOptions = with types; submodule {
+    options = {
+      plugin = mkOption {
+        type = path;
         description = ''
-          Host to listen on without SSL.
+          Plugin path to load, should be a <literal>.so</literal> file.
         '';
       };
 
-      port = mkOption {
-        default = 1883;
-        example = 1883;
-        type = types.int;
+      denySpecialChars = mkOption {
+        type = bool;
         description = ''
-          Port on which to listen without SSL.
+          Automatically disallow all clients using <literal>#</literal>
+          or <literal>+</literal> in their name/id.
         '';
+        default = true;
       };
 
-      ssl = {
-        enable = mkEnableOption "SSL listener";
+      options = mkOption {
+        type = attrsOf optionType;
+        description = ''
+          Options for the auth plugin. Each key turns into a <literal>auth_opt_*</literal>
+           line in the config.
+        '';
+        default = {};
+      };
+    };
+  };
 
-        cafile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = "Path to PEM encoded CA certificates.";
-        };
+  authAsserts = prefix: auth:
+    mapAttrsToList
+      (n: _: {
+        assertion = configKey.check n;
+        message = "Invalid auth plugin key ${prefix}.${n}";
+      })
+      auth;
+
+  formatAuthPlugin = plugin:
+    [
+      "auth_plugin ${plugin.plugin}"
+      "auth_plugin_deny_special_chars ${optionToString plugin.denySpecialChars}"
+    ]
+    ++ formatFreeform { prefix = "auth_opt_"; } plugin.options;
+
+  freeformListenerKeys = {
+    allow_anonymous = 1;
+    allow_zero_length_clientid = 1;
+    auto_id_prefix = 1;
+    cafile = 1;
+    capath = 1;
+    certfile = 1;
+    ciphers = 1;
+    "ciphers_tls1.3" = 1;
+    crlfile = 1;
+    dhparamfile = 1;
+    http_dir = 1;
+    keyfile = 1;
+    max_connections = 1;
+    max_qos = 1;
+    max_topic_alias = 1;
+    mount_point = 1;
+    protocol = 1;
+    psk_file = 1;
+    psk_hint = 1;
+    require_certificate = 1;
+    socket_domain = 1;
+    tls_engine = 1;
+    tls_engine_kpass_sha1 = 1;
+    tls_keyform = 1;
+    tls_version = 1;
+    use_identity_as_username = 1;
+    use_subject_as_username = 1;
+    use_username_as_clientid = 1;
+  };
 
-        certfile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = "Path to PEM encoded server certificate.";
-        };
+  listenerOptions = with types; submodule {
+    options = {
+      port = mkOption {
+        type = port;
+        description = ''
+          Port to listen on. Must be set to 0 to listen on a unix domain socket.
+        '';
+        default = 1883;
+      };
 
-        keyfile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = "Path to PEM encoded server key.";
-        };
+      address = mkOption {
+        type = nullOr str;
+        description = ''
+          Address to listen on. Listen on <literal>0.0.0.0</literal>/<literal>::</literal>
+          when unset.
+        '';
+        default = null;
+      };
 
-        host = mkOption {
-          default = "0.0.0.0";
-          example = "localhost";
-          type = types.str;
-          description = ''
-            Host to listen on with SSL.
-          '';
-        };
+      authPlugins = mkOption {
+        type = listOf authPluginOptions;
+        description = ''
+          Authentication plugin to attach to this listener.
+          Refer to the <link xlink:href="https://mosquitto.org/man/mosquitto-conf-5.html">
+          mosquitto.conf documentation</link> for details on authentication plugins.
+        '';
+        default = [];
+      };
 
-        port = mkOption {
-          default = 8883;
-          example = 8883;
-          type = types.int;
-          description = ''
-            Port on which to listen with SSL.
-          '';
-        };
+      users = mkOption {
+        type = attrsOf userOptions;
+        example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; };
+        description = ''
+          A set of users and their passwords and ACLs.
+        '';
+        default = {};
       };
 
-      dataDir = mkOption {
-        default = "/var/lib/mosquitto";
-        type = types.path;
+      omitPasswordAuth = mkOption {
+        type = bool;
         description = ''
-          The data directory.
+          Omits password checking, allowing anyone to log in with any user name unless
+          other mandatory authentication methods (eg TLS client certificates) are configured.
         '';
+        default = false;
       };
 
-      users = mkOption {
-        type = types.attrsOf (types.submodule {
-          options = {
-            password = mkOption {
-              type = with types; uniq (nullOr str);
-              default = null;
-              description = ''
-                Specifies the (clear text) password for the MQTT User.
-              '';
-            };
+      acl = mkOption {
+        type = listOf str;
+        description = ''
+          Additional ACL items to prepend to the generated ACL file.
+        '';
+        example = [ "pattern read #" "topic readwrite anon/report/#" ];
+        default = [];
+      };
 
-            passwordFile = mkOption {
-              type = with types; uniq (nullOr str);
-              example = "/path/to/file";
-              default = null;
-              description = ''
-                Specifies the path to a file containing the
-                clear text password for the MQTT user.
-              '';
-            };
+      settings = mkOption {
+        type = submodule {
+          freeformType = attrsOf optionType;
+        };
+        description = ''
+          Additional settings for this listener.
+        '';
+        default = {};
+      };
+    };
+  };
 
-            hashedPassword = mkOption {
-              type = with types; uniq (nullOr str);
-              default = null;
-              description = ''
-                Specifies the hashed password for the MQTT User.
-                To generate hashed password install <literal>mosquitto</literal>
-                package and use <literal>mosquitto_passwd</literal>.
-              '';
-            };
+  listenerAsserts = prefix: listener:
+    assertKeysValid prefix freeformListenerKeys listener.settings
+    ++ userAsserts prefix listener.users
+    ++ imap0
+      (i: v: authAsserts "${prefix}.authPlugins.${toString i}" v)
+      listener.authPlugins;
+
+  formatListener = idx: listener:
+    [
+      "listener ${toString listener.port} ${toString listener.address}"
+      "acl_file ${makeACLFile idx listener.users listener.acl}"
+    ]
+    ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}"
+    ++ formatFreeform {} listener.settings
+    ++ concatMap formatAuthPlugin listener.authPlugins;
+
+  freeformBridgeKeys = {
+    bridge_alpn = 1;
+    bridge_attempt_unsubscribe = 1;
+    bridge_bind_address = 1;
+    bridge_cafile = 1;
+    bridge_capath = 1;
+    bridge_certfile = 1;
+    bridge_identity = 1;
+    bridge_insecure = 1;
+    bridge_keyfile = 1;
+    bridge_max_packet_size = 1;
+    bridge_outgoing_retain = 1;
+    bridge_protocol_version = 1;
+    bridge_psk = 1;
+    bridge_require_ocsp = 1;
+    bridge_tls_version = 1;
+    cleansession = 1;
+    idle_timeout = 1;
+    keepalive_interval = 1;
+    local_cleansession = 1;
+    local_clientid = 1;
+    local_password = 1;
+    local_username = 1;
+    notification_topic = 1;
+    notifications = 1;
+    notifications_local_only = 1;
+    remote_clientid = 1;
+    remote_password = 1;
+    remote_username = 1;
+    restart_timeout = 1;
+    round_robin = 1;
+    start_type = 1;
+    threshold = 1;
+    try_private = 1;
+  };
 
-            hashedPasswordFile = mkOption {
-              type = with types; uniq (nullOr str);
-              example = "/path/to/file";
-              default = null;
+  bridgeOptions = with types; submodule {
+    options = {
+      addresses = mkOption {
+        type = listOf (submodule {
+          options = {
+            address = mkOption {
+              type = str;
               description = ''
-                Specifies the path to a file containing the
-                hashed password for the MQTT user.
-                To generate hashed password install <literal>mosquitto</literal>
-                package and use <literal>mosquitto_passwd</literal>.
+                Address of the remote MQTT broker.
               '';
             };
 
-            acl = mkOption {
-              type = types.listOf types.str;
-              example = [ "topic read A/B" "topic A/#" ];
+            port = mkOption {
+              type = port;
               description = ''
-                Control client access to topics on the broker.
+                Port of the remote MQTT broker.
               '';
+              default = 1883;
             };
           };
         });
-        example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
+        default = [];
         description = ''
-          A set of users and their passwords and ACLs.
+          Remote endpoints for the bridge.
         '';
       };
 
-      allowAnonymous = mkOption {
-        default = false;
-        type = types.bool;
+      topics = mkOption {
+        type = listOf str;
         description = ''
-          Allow clients to connect without authentication.
+          Topic patterns to be shared between the two brokers.
+          Refer to the <link xlink:href="https://mosquitto.org/man/mosquitto-conf-5.html">
+          mosquitto.conf documentation</link> for details on the format.
         '';
+        default = [];
+        example = [ "# both 2 local/topic/ remote/topic/" ];
       };
 
-      checkPasswords = mkOption {
-        default = false;
-        example = true;
-        type = types.bool;
+      settings = mkOption {
+        type = submodule {
+          freeformType = attrsOf optionType;
+        };
         description = ''
-          Refuse connection when clients provide incorrect passwords.
+          Additional settings for this bridge.
         '';
+        default = {};
       };
+    };
+  };
 
-      extraConf = mkOption {
-        default = "";
-        type = types.lines;
-        description = ''
-          Extra config to append to `mosquitto.conf` file.
-        '';
-      };
+  bridgeAsserts = prefix: bridge:
+    assertKeysValid prefix freeformBridgeKeys bridge.settings
+    ++ [ {
+      assertion = length bridge.addresses > 0;
+      message = "Bridge ${prefix} needs remote broker addresses";
+    } ];
+
+  formatBridge = name: bridge:
+    [
+      "connection ${name}"
+      "addresses ${concatMapStringsSep " " (a: "${a.address}:${toString a.port}") bridge.addresses}"
+    ]
+    ++ map (t: "topic ${t}") bridge.topics
+    ++ formatFreeform {} bridge.settings;
+
+  freeformGlobalKeys = {
+    allow_duplicate_messages = 1;
+    autosave_interval = 1;
+    autosave_on_changes = 1;
+    check_retain_source = 1;
+    connection_messages = 1;
+    log_facility = 1;
+    log_timestamp = 1;
+    log_timestamp_format = 1;
+    max_inflight_bytes = 1;
+    max_inflight_messages = 1;
+    max_keepalive = 1;
+    max_packet_size = 1;
+    max_queued_bytes = 1;
+    max_queued_messages = 1;
+    memory_limit = 1;
+    message_size_limit = 1;
+    persistence_file = 1;
+    persistence_location = 1;
+    persistent_client_expiration = 1;
+    pid_file = 1;
+    queue_qos0_messages = 1;
+    retain_available = 1;
+    set_tcp_nodelay = 1;
+    sys_interval = 1;
+    upgrade_outgoing_qos = 1;
+    websockets_headers_size = 1;
+    websockets_log_level = 1;
+  };
 
-      aclExtraConf = mkOption {
-        default = "";
-        type = types.lines;
-        description = ''
-          Extra config to prepend to the ACL file.
-        '';
-      };
+  globalOptions = with types; {
+    enable = mkEnableOption "the MQTT Mosquitto broker";
+
+    bridges = mkOption {
+      type = attrsOf bridgeOptions;
+      default = {};
+      description = ''
+        Bridges to build to other MQTT brokers.
+      '';
+    };
+
+    listeners = mkOption {
+      type = listOf listenerOptions;
+      default = {};
+      description = ''
+        Listeners to configure on this broker.
+      '';
+    };
+
+    includeDirs = mkOption {
+      type = listOf path;
+      description = ''
+        Directories to be scanned for further config files to include.
+        Directories will processed in the order given,
+        <literal>*.conf</literal> files in the directory will be
+        read in case-sensistive alphabetical order.
+      '';
+      default = [];
+    };
+
+    logDest = mkOption {
+      type = listOf (either path (enum [ "stdout" "stderr" "syslog" "topic" "dlt" ]));
+      description = ''
+        Destinations to send log messages to.
+      '';
+      default = [ "stderr" ];
+    };
+
+    logType = mkOption {
+      type = listOf (enum [ "debug" "error" "warning" "notice" "information"
+                            "subscribe" "unsubscribe" "websockets" "none" "all" ]);
+      description = ''
+        Types of messages to log.
+      '';
+      default = [];
+    };
+
+    persistence = mkOption {
+      type = bool;
+      description = ''
+        Enable persistent storage of subscriptions and messages.
+      '';
+      default = true;
+    };
 
+    dataDir = mkOption {
+      default = "/var/lib/mosquitto";
+      type = types.path;
+      description = ''
+        The data directory.
+      '';
+    };
+
+    settings = mkOption {
+      type = submodule {
+        freeformType = attrsOf optionType;
+      };
+      description = ''
+        Global configuration options for the mosquitto broker.
+      '';
+      default = {};
     };
   };
 
+  globalAsserts = prefix: cfg:
+    flatten [
+      (assertKeysValid prefix freeformGlobalKeys cfg.settings)
+      (imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners)
+      (mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges)
+    ];
+
+  formatGlobal = cfg:
+    [
+      "per_listener_settings true"
+      "persistence ${optionToString cfg.persistence}"
+    ]
+    ++ map
+      (d: if path.check d then "log_dest file ${d}" else "log_dest ${d}")
+      cfg.logDest
+    ++ map (t: "log_type ${t}") cfg.logType
+    ++ formatFreeform {} cfg.settings
+    ++ concatLists (imap0 formatListener cfg.listeners)
+    ++ concatLists (mapAttrsToList formatBridge cfg.bridges)
+    ++ map (d: "include_dir ${d}") cfg.includeDirs;
+
+  configFile = pkgs.writeText "mosquitto.conf"
+    (concatStringsSep "\n" (formatGlobal cfg));
+
+in
+
+{
+
+  ###### Interface
+
+  options.services.mosquitto = globalOptions;
 
   ###### Implementation
 
   config = mkIf cfg.enable {
 
-    assertions = mapAttrsToList (name: cfg: {
-      assertion = length (filter (s: s != null) (with cfg; [
-        password passwordFile hashedPassword hashedPasswordFile
-      ])) <= 1;
-      message = "Cannot set more than one password option";
-    }) cfg.users;
+    assertions = globalAsserts "services.mosquitto" cfg;
 
     systemd.services.mosquitto = {
       description = "Mosquitto MQTT Broker Daemon";
@@ -229,7 +565,7 @@ in
         RuntimeDirectory = "mosquitto";
         WorkingDirectory = cfg.dataDir;
         Restart = "on-failure";
-        ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
+        ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${configFile}";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
 
         # Hardening
@@ -254,12 +590,34 @@ in
         ReadWritePaths = [
           cfg.dataDir
           "/tmp"  # mosquitto_passwd creates files in /tmp before moving them
-        ];
-        ReadOnlyPaths = with cfg.ssl; lib.optionals (enable) [
-          certfile
-          keyfile
-          cafile
-        ];
+        ] ++ filter path.check cfg.logDest;
+        ReadOnlyPaths =
+          map (p: "${p}")
+            (cfg.includeDirs
+             ++ filter
+               (v: v != null)
+               (flatten [
+                 (map
+                   (l: [
+                     (l.settings.psk_file or null)
+                     (l.settings.http_dir or null)
+                     (l.settings.cafile or null)
+                     (l.settings.capath or null)
+                     (l.settings.certfile or null)
+                     (l.settings.crlfile or null)
+                     (l.settings.dhparamfile or null)
+                     (l.settings.keyfile or null)
+                   ])
+                   cfg.listeners)
+                 (mapAttrsToList
+                   (_: b: [
+                     (b.settings.bridge_cafile or null)
+                     (b.settings.bridge_capath or null)
+                     (b.settings.bridge_certfile or null)
+                     (b.settings.bridge_keyfile or null)
+                   ])
+                   cfg.bridges)
+               ]));
         RemoveIPC = true;
         RestrictAddressFamilies = [
           "AF_UNIX"  # for sd_notify() call
@@ -277,20 +635,12 @@ in
         ];
         UMask = "0077";
       };
-      preStart = ''
-        rm -f ${cfg.dataDir}/passwd
-        touch ${cfg.dataDir}/passwd
-      '' + concatStringsSep "\n" (
-        mapAttrsToList (n: c:
-          if c.hashedPasswordFile != null then
-            "echo '${n}:'$(cat '${c.hashedPasswordFile}') >> ${cfg.dataDir}/passwd"
-          else if c.passwordFile != null then
-            "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} $(cat '${c.passwordFile}')"
-          else if c.hashedPassword != null then
-            "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
-          else optionalString (c.password != null)
-            "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'"
-        ) cfg.users);
+      preStart =
+        concatStringsSep
+          "\n"
+          (imap0
+            (idx: listener: makePasswordFile listener.users "${cfg.dataDir}/passwd-${toString idx}")
+            cfg.listeners);
     };
 
     users.users.mosquitto = {
@@ -304,4 +654,11 @@ in
     users.groups.mosquitto.gid = config.ids.gids.mosquitto;
 
   };
+
+  meta = {
+    maintainers = with lib.maintainers; [ pennae ];
+    # Don't edit the docbook xml directly, edit the md and generate it:
+    # `pandoc mosquitto.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > mosquitto.xml`
+    doc = ./mosquitto.xml;
+  };
 }
diff --git a/nixos/modules/services/networking/mosquitto.xml b/nixos/modules/services/networking/mosquitto.xml
new file mode 100644
index 0000000000000..d16ab28c02697
--- /dev/null
+++ b/nixos/modules/services/networking/mosquitto.xml
@@ -0,0 +1,147 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-mosquitto">
+  <title>Mosquitto</title>
+  <para>
+    Mosquitto is a MQTT broker often used for IoT or home automation
+    data transport.
+  </para>
+  <section xml:id="module-services-mosquitto-quickstart">
+    <title>Quickstart</title>
+    <para>
+      A minimal configuration for Mosquitto is
+    </para>
+    <programlisting language="bash">
+services.mosquitto = {
+  enable = true;
+  listeners = [ {
+    acl = [ &quot;pattern readwrite #&quot; ];
+    omitPasswordAuth = true;
+    settings.allow_anonymous = true;
+  } ];
+};
+</programlisting>
+    <para>
+      This will start a broker on port 1883, listening on all interfaces
+      of the machine, allowing read/write access to all topics to any
+      user without password requirements.
+    </para>
+    <para>
+      User authentication can be configured with the
+      <literal>users</literal> key of listeners. A config that gives
+      full read access to a user <literal>monitor</literal> and
+      restricted write access to a user <literal>service</literal> could
+      look like
+    </para>
+    <programlisting language="bash">
+services.mosquitto = {
+  enable = true;
+  listeners = [ {
+    users = {
+      monitor = {
+        acl = [ &quot;read #&quot; ];
+        password = &quot;monitor&quot;;
+      };
+      service = {
+        acl = [ &quot;write service/#&quot; ];
+        password = &quot;service&quot;;
+      };
+    };
+  } ];
+};
+</programlisting>
+    <para>
+      TLS authentication is configured by setting TLS-related options of
+      the listener:
+    </para>
+    <programlisting language="bash">
+services.mosquitto = {
+  enable = true;
+  listeners = [ {
+    port = 8883; # port change is not required, but helpful to avoid mistakes
+    # ...
+    settings = {
+      cafile = &quot;/path/to/mqtt.ca.pem&quot;;
+      certfile = &quot;/path/to/mqtt.pem&quot;;
+      keyfile = &quot;/path/to/mqtt.key&quot;;
+    };
+  } ];
+</programlisting>
+  </section>
+  <section xml:id="module-services-mosquitto-config">
+    <title>Configuration</title>
+    <para>
+      The Mosquitto configuration has four distinct types of settings:
+      the global settings of the daemon, listeners, plugins, and
+      bridges. Bridges and listeners are part of the global
+      configuration, plugins are part of listeners. Users of the broker
+      are configured as parts of listeners rather than globally,
+      allowing configurations in which a given user is only allowed to
+      log in to the broker using specific listeners (eg to configure an
+      admin user with full access to all topics, but restricted to
+      localhost).
+    </para>
+    <para>
+      Almost all options of Mosquitto are available for configuration at
+      their appropriate levels, some as NixOS options written in camel
+      case, the remainders under <literal>settings</literal> with their
+      exact names in the Mosquitto config file. The exceptions are
+      <literal>acl_file</literal> (which is always set according to the
+      <literal>acl</literal> attributes of a listener and its users) and
+      <literal>per_listener_settings</literal> (which is always set to
+      <literal>true</literal>).
+    </para>
+    <section xml:id="module-services-mosquitto-config-passwords">
+      <title>Password authentication</title>
+      <para>
+        Mosquitto can be run in two modes, with a password file or
+        without. Each listener has its own password file, and different
+        listeners may use different password files. Password file
+        generation can be disabled by setting
+        <literal>omitPasswordAuth = true</literal> for a listener; in
+        this case it is necessary to either set
+        <literal>settings.allow_anonymous = true</literal> to allow all
+        logins, or to configure other authentication methods like TLS
+        client certificates with
+        <literal>settings.use_identity_as_username = true</literal>.
+      </para>
+      <para>
+        The default is to generate a password file for each listener
+        from the users configured to that listener. Users with no
+        configured password will not be added to the password file and
+        thus will not be able to use the broker.
+      </para>
+    </section>
+    <section xml:id="module-services-mosquitto-config-acl">
+      <title>ACL format</title>
+      <para>
+        Every listener has a Mosquitto <literal>acl_file</literal>
+        attached to it. This ACL is configured via two attributes of the
+        config:
+      </para>
+      <itemizedlist spacing="compact">
+        <listitem>
+          <para>
+            the <literal>acl</literal> attribute of the listener
+            configures pattern ACL entries and topic ACL entries for
+            anonymous users. Each entry must be prefixed with
+            <literal>pattern</literal> or <literal>topic</literal> to
+            distinguish between these two cases.
+          </para>
+        </listitem>
+        <listitem>
+          <para>
+            the <literal>acl</literal> attribute of every user
+            configures in the listener configured the ACL for that given
+            user. Only topic ACLs are supported by Mosquitto in this
+            setting, so no prefix is required or allowed.
+          </para>
+        </listitem>
+      </itemizedlist>
+      <para>
+        The default ACL for a listener is empty, disallowing all
+        accesses from all clients. To configure a completely open ACL,
+        set <literal>acl = [ &quot;pattern readwrite #&quot; ]</literal>
+        in the listener.
+      </para>
+    </section>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
new file mode 100644
index 0000000000000..1cc2ad1fc849c
--- /dev/null
+++ b/nixos/modules/services/networking/multipath.nix
@@ -0,0 +1,572 @@
+{ config, lib, pkgs, ... }: with lib;
+
+# See http://christophe.varoqui.free.fr/usage.html and
+# https://github.com/opensvc/multipath-tools/blob/master/multipath/multipath.conf.5
+
+let
+  cfg = config.services.multipath;
+
+  indentLines = n: str: concatStringsSep "\n" (
+    map (line: "${fixedWidthString n " " " "}${line}") (
+      filter ( x: x != "" ) ( splitString "\n" str )
+    )
+  );
+
+  addCheckDesc = desc: elemType: check: types.addCheck elemType check
+    // { description = "${elemType.description} (with check: ${desc})"; };
+  hexChars = stringToCharacters "0123456789abcdef";
+  isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
+  hexStr = addCheckDesc "hexadecimal string" types.str isHexString;
+
+in {
+
+  options.services.multipath = with types; {
+
+    enable = mkEnableOption "the device mapper multipath (DM-MP) daemon";
+
+    package = mkOption {
+      type = package;
+      description = "multipath-tools package to use";
+      default = pkgs.multipath-tools;
+      defaultText = "pkgs.multipath-tools";
+    };
+
+    devices = mkOption {
+      default = [ ];
+      example = literalExpression ''
+        [
+          {
+            vendor = "\"COMPELNT\"";
+            product = "\"Compellent Vol\"";
+            path_checker = "tur";
+            no_path_retry = "queue";
+            max_sectors_kb = 256;
+          }, ...
+        ]
+      '';
+      description = ''
+        This option allows you to define arrays for use in multipath
+        groups.
+      '';
+      type = listOf (submodule {
+        options = {
+
+          vendor = mkOption {
+            type = str;
+            example = "COMPELNT";
+            description = "Regular expression to match the vendor name";
+          };
+
+          product = mkOption {
+            type = str;
+            example = "Compellent Vol";
+            description = "Regular expression to match the product name";
+          };
+
+          revision = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Regular expression to match the product revision";
+          };
+
+          product_blacklist = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Products with the given vendor matching this string are blacklisted";
+          };
+
+          alias_prefix = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The user_friendly_names prefix to use for this device type, instead of the default mpath";
+          };
+
+          vpd_vendor = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The vendor specific vpd page information, using the vpd page abbreviation";
+          };
+
+          hardware_handler = mkOption {
+            type = nullOr (enum [ "emc" "rdac" "hp_sw" "alua" "ana" ]);
+            default = null;
+            description = "The hardware handler to use for this device type";
+          };
+
+          # Optional arguments
+          path_grouping_policy = mkOption {
+            type = nullOr (enum [ "failover" "multibus" "group_by_serial" "group_by_prio" "group_by_node_name" ]);
+            default = null; # real default: "failover"
+            description = "The default path grouping policy to apply to unspecified multipaths";
+          };
+
+          uid_attribute = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The udev attribute providing a unique path identifier (WWID)";
+          };
+
+          getuid_callout = mkOption {
+            type = nullOr str;
+            default = null;
+            description = ''
+              (Superseded by uid_attribute) The default program and args to callout
+              to obtain a unique path identifier. Should be specified with an absolute path.
+            '';
+          };
+
+          path_selector = mkOption {
+            type = nullOr (enum [
+              ''"round-robin 0"''
+              ''"queue-length 0"''
+              ''"service-time 0"''
+              ''"historical-service-time 0"''
+            ]);
+            default = null; # real default: "service-time 0"
+            description = "The default path selector algorithm to use; they are offered by the kernel multipath target";
+          };
+
+          path_checker = mkOption {
+            type = enum [ "readsector0" "tur" "emc_clariion" "hp_sw" "rdac" "directio" "cciss_tur" "none" ];
+            default = "tur";
+            description = "The default method used to determine the paths state";
+          };
+
+          prio = mkOption {
+            type = nullOr (enum [
+              "none" "const" "sysfs" "emc" "alua" "ontap" "rdac" "hp_sw" "hds"
+              "random" "weightedpath" "path_latency" "ana" "datacore" "iet"
+            ]);
+            default = null; # real default: "const"
+            description = "The name of the path priority routine";
+          };
+
+          prio_args = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Arguments to pass to to the prio function";
+          };
+
+          features = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Specify any device-mapper features to be used";
+          };
+
+          failback = mkOption {
+            type = nullOr str;
+            default = null; # real default: "manual"
+            description = "Tell multipathd how to manage path group failback. Quote integers as strings";
+          };
+
+          rr_weight = mkOption {
+            type = nullOr (enum [ "priorities" "uniform" ]);
+            default = null; # real default: "uniform"
+            description = ''
+              If set to priorities the multipath configurator will assign path weights
+              as "path prio * rr_min_io".
+            '';
+          };
+
+          no_path_retry = mkOption {
+            type = nullOr str;
+            default = null; # real default: "fail"
+            description = "Specify what to do when all paths are down. Quote integers as strings";
+          };
+
+          rr_min_io = mkOption {
+            type = nullOr int;
+            default = null; # real default: 1000
+            description = ''
+              Number of I/O requests to route to a path before switching to the next in the
+              same path group. This is only for Block I/O (BIO) based multipath and
+              only apply to round-robin path_selector.
+            '';
+          };
+
+          rr_min_io_rq = mkOption {
+            type = nullOr int;
+            default = null; # real default: 1
+            description = ''
+              Number of I/O requests to route to a path before switching to the next in the
+              same path group. This is only for Request based multipath and
+              only apply to round-robin path_selector.
+            '';
+          };
+
+          fast_io_fail_tmo = mkOption {
+            type = nullOr str;
+            default = null; # real default: 5
+            description = ''
+              Specify the number of seconds the SCSI layer will wait after a problem has been
+              detected on a FC remote port before failing I/O to devices on that remote port.
+              This should be smaller than dev_loss_tmo. Setting this to "off" will disable
+              the timeout. Quote integers as strings.
+            '';
+          };
+
+          dev_loss_tmo = mkOption {
+            type = nullOr str;
+            default = null; # real default: 600
+            description = ''
+              Specify the number of seconds the SCSI layer will wait after a problem has
+              been detected on a FC remote port before removing it from the system. This
+              can be set to "infinity" which sets it to the max value of 2147483647
+              seconds, or 68 years. It will be automatically adjusted to the overall
+              retry interval no_path_retry * polling_interval
+              if a number of retries is given with no_path_retry and the
+              overall retry interval is longer than the specified dev_loss_tmo value.
+              The Linux kernel will cap this value to 600 if fast_io_fail_tmo
+              is not set.
+            '';
+          };
+
+          flush_on_last_del = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "no"
+            description = ''
+              If set to "yes" multipathd will disable queueing when the last path to a
+              device has been deleted.
+            '';
+          };
+
+          user_friendly_names = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "no"
+            description = ''
+              If set to "yes", using the bindings file /etc/multipath/bindings
+              to assign a persistent and unique alias to the multipath, in the
+              form of mpath. If set to "no" use the WWID as the alias. In either
+              case this be will be overridden by any specific aliases in the
+              multipaths section.
+            '';
+          };
+
+          retain_attached_hw_handler = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "yes"
+            description = ''
+              (Obsolete for kernels >= 4.3) If set to "yes" and the SCSI layer has
+              already attached a hardware_handler to the device, multipath will not
+              force the device to use the hardware_handler specified by mutipath.conf.
+              If the SCSI layer has not attached a hardware handler, multipath will
+              continue to use its configured hardware handler.
+
+              Important Note: Linux kernel 4.3 or newer always behaves as if
+              "retain_attached_hw_handler yes" was set.
+            '';
+          };
+
+          detect_prio = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "yes"
+            description = ''
+              If set to "yes", multipath will try to detect if the device supports
+              SCSI-3 ALUA. If so, the device will automatically use the sysfs
+              prioritizer if the required sysf attributes access_state and
+              preferred_path are supported, or the alua prioritizer if not. If set
+              to "no", the prioritizer will be selected as usual.
+            '';
+          };
+
+          detect_checker = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "yes"
+            description = ''
+              If set to "yes", multipath will try to detect if the device supports
+              SCSI-3 ALUA. If so, the device will automatically use the tur checker.
+              If set to "no", the checker will be selected as usual.
+            '';
+          };
+
+          deferred_remove = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "no"
+            description = ''
+              If set to "yes", multipathd will do a deferred remove instead of a
+              regular remove when the last path device has been deleted. This means
+              that if the multipath device is still in use, it will be freed when
+              the last user closes it. If path is added to the multipath device
+              before the last user closes it, the deferred remove will be canceled.
+            '';
+          };
+
+          san_path_err_threshold = mkOption {
+            type = nullOr str;
+            default = null;
+            description = ''
+              If set to a value greater than 0, multipathd will watch paths and check
+              how many times a path has been failed due to errors.If the number of
+              failures on a particular path is greater then the san_path_err_threshold,
+              then the path will not reinstate till san_path_err_recovery_time. These
+              path failures should occur within a san_path_err_forget_rate checks, if
+              not we will consider the path is good enough to reinstantate.
+            '';
+          };
+
+          san_path_err_forget_rate = mkOption {
+            type = nullOr str;
+            default = null;
+            description = ''
+              If set to a value greater than 0, multipathd will check whether the path
+              failures has exceeded the san_path_err_threshold within this many checks
+              i.e san_path_err_forget_rate. If so we will not reinstante the path till
+              san_path_err_recovery_time.
+            '';
+          };
+
+          san_path_err_recovery_time = mkOption {
+            type = nullOr str;
+            default = null;
+            description = ''
+              If set to a value greater than 0, multipathd will make sure that when
+              path failures has exceeded the san_path_err_threshold within
+              san_path_err_forget_rate then the path will be placed in failed state
+              for san_path_err_recovery_time duration. Once san_path_err_recovery_time
+              has timeout we will reinstante the failed path. san_path_err_recovery_time
+              value should be in secs.
+            '';
+          };
+
+          marginal_path_err_sample_time = mkOption {
+            type = nullOr int;
+            default = null;
+            description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+          };
+
+          marginal_path_err_rate_threshold = mkOption {
+            type = nullOr int;
+            default = null;
+            description = "The error rate threshold as a permillage (1/1000)";
+          };
+
+          marginal_path_err_recheck_gap_time = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+          };
+
+          marginal_path_double_failed_time = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+          };
+
+          delay_watch_checks = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "This option is deprecated, and mapped to san_path_err_forget_rate";
+          };
+
+          delay_wait_checks = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "This option is deprecated, and mapped to san_path_err_recovery_time";
+          };
+
+          skip_kpartx = mkOption {
+            type = nullOr (enum [ "yes" "no" ]);
+            default = null; # real default: "no"
+            description = "If set to yes, kpartx will not automatically create partitions on the device";
+          };
+
+          max_sectors_kb = mkOption {
+            type = nullOr int;
+            default = null;
+            description = "Sets the max_sectors_kb device parameter on all path devices and the multipath device to the specified value";
+          };
+
+          ghost_delay = mkOption {
+            type = nullOr int;
+            default = null;
+            description = "Sets the number of seconds that multipath will wait after creating a device with only ghost paths before marking it ready for use in systemd";
+          };
+
+          all_tg_pt = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Set the 'all targets ports' flag when registering keys with mpathpersist";
+          };
+
+        };
+      });
+    };
+
+    defaults = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        This section defines default values for attributes which are used
+        whenever no values are given in the appropriate device or multipath
+        sections.
+      '';
+    };
+
+    blacklist = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        This section defines which devices should be excluded from the
+        multipath topology discovery.
+      '';
+    };
+
+    blacklist_exceptions = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        This section defines which devices should be included in the
+        multipath topology discovery, despite being listed in the
+        blacklist section.
+      '';
+    };
+
+    overrides = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        This section defines values for attributes that should override the
+        device-specific settings for all devices.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = nullOr str;
+      default = null;
+      description = "Lines to append to default multipath.conf";
+    };
+
+    extraConfigFile = mkOption {
+      type = nullOr str;
+      default = null;
+      description = "Append an additional file's contents to /etc/multipath.conf";
+    };
+
+    pathGroups = mkOption {
+      example = literalExpression ''
+        [
+          {
+            wwid = "360080e500043b35c0123456789abcdef";
+            alias = 10001234;
+            array = "bigarray.example.com";
+            fsType = "zfs"; # optional
+            options = "ro"; # optional
+          }, ...
+        ]
+      '';
+      description = ''
+        This option allows you to define multipath groups as described
+        in http://christophe.varoqui.free.fr/usage.html.
+      '';
+      type = listOf (submodule {
+        options = {
+
+          alias = mkOption {
+            type = int;
+            example = 1001234;
+            description = "The name of the multipath device";
+          };
+
+          wwid = mkOption {
+            type = hexStr;
+            example = "360080e500043b35c0123456789abcdef";
+            description = "The identifier for the multipath device";
+          };
+
+          array = mkOption {
+            type = str;
+            default = null;
+            example = "bigarray.example.com";
+            description = "The DNS name of the storage array";
+          };
+
+          fsType = mkOption {
+            type = nullOr str;
+            default = null;
+            example = "zfs";
+            description = "Type of the filesystem";
+          };
+
+          options = mkOption {
+            type = nullOr str;
+            default = null;
+            example = "ro";
+            description = "Options used to mount the file system";
+          };
+
+        };
+      });
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."multipath.conf".text =
+      let
+        inherit (cfg) defaults blacklist blacklist_exceptions overrides;
+
+        mkDeviceBlock = cfg: let
+          nonNullCfg = lib.filterAttrs (k: v: v != null) cfg;
+          attrs = lib.mapAttrsToList (name: value: "  ${name} ${toString value}") nonNullCfg;
+        in ''
+          device {
+          ${lib.concatStringsSep "\n" attrs}
+          }
+        '';
+        devices = lib.concatMapStringsSep "\n" mkDeviceBlock cfg.devices;
+
+        mkMultipathBlock = m: ''
+          multipath {
+            wwid ${m.wwid}
+            alias ${toString m.alias}
+          }
+        '';
+        multipaths = lib.concatMapStringsSep "\n" mkMultipathBlock cfg.pathGroups;
+
+      in ''
+        devices {
+        ${indentLines 2 devices}
+        }
+
+        ${optionalString (!isNull defaults) ''
+          defaults {
+          ${indentLines 2 defaults}
+            multipath_dir ${cfg.package}/lib/multipath
+          }
+        ''}
+        ${optionalString (!isNull blacklist) ''
+          blacklist {
+          ${indentLines 2 blacklist}
+          }
+        ''}
+        ${optionalString (!isNull blacklist_exceptions) ''
+          blacklist_exceptions {
+          ${indentLines 2 blacklist_exceptions}
+          }
+        ''}
+        ${optionalString (!isNull overrides) ''
+          overrides {
+          ${indentLines 2 overrides}
+          }
+        ''}
+        multipaths {
+        ${indentLines 2 multipaths}
+        }
+      '';
+
+    systemd.packages = [ cfg.package ];
+
+    environment.systemPackages = [ cfg.package ];
+    boot.kernelModules = [ "dm-multipath" "dm-service-time" ];
+
+    # We do not have systemd in stage-1 boot so must invoke `multipathd`
+    # with the `-1` argument which disables systemd calls. Invoke `multipath`
+    # to display the multipath mappings in the output of `journalctl -b`.
+    boot.initrd.kernelModules = [ "dm-multipath" "dm-service-time" ];
+    boot.initrd.postDeviceCommands = ''
+      modprobe -a dm-multipath dm-service-time
+      multipathd -s
+      (set -x && sleep 1 && multipath -ll)
+    '';
+  };
+}
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index f8bb878ec65da..bbbe1e181bba1 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -112,7 +112,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.murmur;
-        defaultText = "pkgs.murmur";
+        defaultText = literalExpression "pkgs.murmur";
         description = "Overridable attribute of the murmur package to use.";
       };
 
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
index f29d190c62623..803f0689d1fd1 100644
--- a/nixos/modules/services/networking/mxisd.nix
+++ b/nixos/modules/services/networking/mxisd.nix
@@ -42,7 +42,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.ma1sd;
-        defaultText = "pkgs.ma1sd";
+        defaultText = literalExpression "pkgs.ma1sd";
         description = "The mxisd/ma1sd package to use";
       };
 
diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix
index 45eb500fe8ce9..2e58cd699b256 100644
--- a/nixos/modules/services/networking/nat.nix
+++ b/nixos/modules/services/networking/nat.nix
@@ -247,7 +247,7 @@ in
           loopbackIPs = mkOption {
             type = types.listOf types.str;
             default = [];
-            example = literalExample ''[ "55.1.2.3" ]'';
+            example = literalExpression ''[ "55.1.2.3" ]'';
             description = "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
           };
         };
diff --git a/nixos/modules/services/networking/nats.nix b/nixos/modules/services/networking/nats.nix
index eb0c65bc65616..3e86a4f07bc70 100644
--- a/nixos/modules/services/networking/nats.nix
+++ b/nixos/modules/services/networking/nats.nix
@@ -43,7 +43,6 @@ in {
 
       port = mkOption {
         default = 4222;
-        example = 4222;
         type = types.port;
         description = ''
           Port on which to listen.
@@ -67,7 +66,7 @@ in {
       settings = mkOption {
         default = { };
         type = format.type;
-        example = literalExample ''
+        example = literalExpression ''
           {
             jetstream = {
               max_mem = "1G";
diff --git a/nixos/modules/services/networking/ncdns.nix b/nixos/modules/services/networking/ncdns.nix
index d30fe0f6f6d19..af17fc0814b2e 100644
--- a/nixos/modules/services/networking/ncdns.nix
+++ b/nixos/modules/services/networking/ncdns.nix
@@ -164,7 +164,7 @@ in
       settings = mkOption {
         type = configType;
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           { # enable webserver
             ncdns.httplistenaddr = ":8202";
 
@@ -245,8 +245,10 @@ in
 
     users.users.ncdns = {
       isSystemUser = true;
+      group = "ncdns";
       description = "ncdns daemon user";
     };
+    users.groups.ncdns = {};
 
     systemd.services.ncdns = {
       description = "ncdns daemon";
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index 77e979a8a424f..6046ac860cfae 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -142,7 +142,7 @@ in {
         messages, and respond to them according to a set of rules.
       '';
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           eth0.rules."1111::/64" = {};
         }
diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index e7ebfe1b4db7d..de4439415cf68 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -30,7 +30,7 @@ in
             package = mkOption {
               type = types.package;
               default = pkgs.nebula;
-              defaultText = "pkgs.nebula";
+              defaultText = literalExpression "pkgs.nebula";
               description = "Nebula derivation to use.";
             };
 
@@ -59,9 +59,7 @@ in
                 The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
                 A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
               '';
-              example = literalExample ''
-                { "192.168.100.1" = [ "100.64.22.11:4242" ]; }
-              '';
+              example = { "192.168.100.1" = [ "100.64.22.11:4242" ]; };
             };
 
             isLighthouse = mkOption {
@@ -77,7 +75,7 @@ in
                 List of IPs of lighthouse hosts this node should report to and query from. This should be empty on lighthouse
                 nodes. The IPs should be the lighthouse's Nebula IPs, not their external IPs.
               '';
-              example = ''[ "192.168.100.1" ]'';
+              example = [ "192.168.100.1" ];
             };
 
             listen.host = mkOption {
@@ -110,14 +108,14 @@ in
               type = types.listOf types.attrs;
               default = [];
               description = "Firewall rules for outbound traffic.";
-              example = ''[ { port = "any"; proto = "any"; host = "any"; } ]'';
+              example = [ { port = "any"; proto = "any"; host = "any"; } ];
             };
 
             firewall.inbound = mkOption {
               type = types.listOf types.attrs;
               default = [];
               description = "Firewall rules for inbound traffic.";
-              example = ''[ { port = "any"; proto = "any"; host = "any"; } ]'';
+              example = [ { port = "any"; proto = "any"; host = "any"; } ];
             };
 
             settings = mkOption {
@@ -128,7 +126,7 @@ in
                 <link xlink:href="https://github.com/slackhq/nebula/blob/master/examples/config.yml"/>
                 for details on supported values.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 {
                   lighthouse.dns = {
                     host = "0.0.0.0";
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index c8861171dd6c9..73e63e2ee99b5 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -353,7 +353,7 @@ in {
           };
         });
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
         [ {
               source = pkgs.writeText "upHook" '''
 
@@ -464,6 +464,7 @@ in {
     users.users = {
       nm-openvpn = {
         uid = config.ids.uids.nm-openvpn;
+        group = "nm-openvpn";
         extraGroups = [ "networkmanager" ];
       };
       nm-iodine = {
@@ -501,13 +502,6 @@ in {
 
     systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
 
-    # override unit as recommended by upstream - see https://github.com/NixOS/nixpkgs/issues/88089
-    # TODO: keep an eye on modem-manager releases as this will eventually be added to the upstream unit
-    systemd.services.ModemManager.serviceConfig.ExecStart = [
-      ""
-      "${pkgs.modemmanager}/sbin/ModemManager --filter-policy=STRICT"
-    ];
-
     systemd.services.NetworkManager-dispatcher = {
       wantedBy = [ "network.target" ];
       restartTriggers = [ configFile overrideNameserversScript ];
@@ -533,7 +527,6 @@ in {
 
       {
         networkmanager.connectionConfig = {
-          "ipv6.ip6-privacy" = 2;
           "ethernet.cloned-mac-address" = cfg.ethernet.macAddress;
           "wifi.cloned-mac-address" = cfg.wifi.macAddress;
           "wifi.powersave" =
diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix
index 72f37c32253e5..eb74d373b0af2 100644
--- a/nixos/modules/services/networking/nftables.nix
+++ b/nixos/modules/services/networking/nftables.nix
@@ -32,6 +32,7 @@ in
     };
     networking.nftables.ruleset = mkOption {
       type = types.lines;
+      default = "";
       example = ''
         # Check out https://wiki.nftables.org/ for better documentation.
         # Table for both IPv4 and IPv6.
@@ -86,6 +87,7 @@ in
         name = "nftables-rules";
         text = cfg.ruleset;
       };
+      defaultText = literalDocBook ''a file with the contents of <option>networking.nftables.ruleset</option>'';
       description =
         ''
           The ruleset file to be used with nftables.  Should be in a format that
diff --git a/nixos/modules/services/networking/ngircd.nix b/nixos/modules/services/networking/ngircd.nix
index 4b2fa77959225..c0b9c98fb4bf5 100644
--- a/nixos/modules/services/networking/ngircd.nix
+++ b/nixos/modules/services/networking/ngircd.nix
@@ -34,7 +34,7 @@ in {
         type = types.package;
 
         default = pkgs.ngircd;
-        defaultText = "pkgs.ngircd";
+        defaultText = literalExpression "pkgs.ngircd";
       };
     };
   };
@@ -52,8 +52,11 @@ in {
     };
 
     users.users.ngircd = {
-      uid = config.ids.uids.ngircd;
+      isSystemUser = true;
+      group = "ngircd";
       description = "ngircd user.";
     };
+    users.groups.ngircd = {};
+
   };
 }
diff --git a/nixos/modules/services/networking/nixops-dns.nix b/nixos/modules/services/networking/nixops-dns.nix
index 2bb1263b7fa28..5e33d872ea452 100644
--- a/nixos/modules/services/networking/nixops-dns.nix
+++ b/nixos/modules/services/networking/nixops-dns.nix
@@ -34,7 +34,6 @@ in
 
           For example "ops" will resolve "vm.ops".
         '';
-        example = "ops";
         default = "ops";
       };
 
diff --git a/nixos/modules/services/networking/nntp-proxy.nix b/nixos/modules/services/networking/nntp-proxy.nix
index cc061bf6e3b91..a5973cd59334f 100644
--- a/nixos/modules/services/networking/nntp-proxy.nix
+++ b/nixos/modules/services/networking/nntp-proxy.nix
@@ -6,8 +6,6 @@ let
 
   inherit (pkgs) nntp-proxy;
 
-  proxyUser = "nntp-proxy";
-
   cfg = config.services.nntp-proxy;
 
   configBool = b: if b then "TRUE" else "FALSE";
@@ -161,7 +159,6 @@ in
           options = {
             username = mkOption {
               type = types.str;
-              default = null;
               description = ''
                 Username
               '';
@@ -169,7 +166,6 @@ in
 
             passwordHash = mkOption {
               type = types.str;
-              default = null;
               example = "$6$GtzE7FrpE$wwuVgFYU.TZH4Rz.Snjxk9XGua89IeVwPQ/fEUD8eujr40q5Y021yhn0aNcsQ2Ifw.BLclyzvzgegopgKcneL0";
               description = ''
                 SHA-512 password hash (can be generated by
@@ -191,15 +187,17 @@ in
         '';
 
         default = {};
-        example = literalExample ''
-          "user1" = {
-            passwordHash = "$6$1l0t5Kn2Dk$appzivc./9l/kjq57eg5UCsBKlcfyCr0zNWYNerKoPsI1d7eAwiT0SVsOVx/CTgaBNT/u4fi2vN.iGlPfv1ek0";
-            maxConnections = 5;
-          };
-          "anotheruser" = {
-            passwordHash = "$6$6lwEsWB.TmsS$W7m1riUx4QrA8pKJz8hvff0dnF1NwtZXgdjmGqA1Dx2MDPj07tI9GNcb0SWlMglE.2/hBgynDdAd/XqqtRqVQ0";
-            maxConnections = 7;
-          };
+        example = literalExpression ''
+          {
+            "user1" = {
+              passwordHash = "$6$1l0t5Kn2Dk$appzivc./9l/kjq57eg5UCsBKlcfyCr0zNWYNerKoPsI1d7eAwiT0SVsOVx/CTgaBNT/u4fi2vN.iGlPfv1ek0";
+              maxConnections = 5;
+            };
+            "anotheruser" = {
+              passwordHash = "$6$6lwEsWB.TmsS$W7m1riUx4QrA8pKJz8hvff0dnF1NwtZXgdjmGqA1Dx2MDPj07tI9GNcb0SWlMglE.2/hBgynDdAd/XqqtRqVQ0";
+              maxConnections = 7;
+            };
+          }
         '';
       };
     };
@@ -210,16 +208,18 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users.${proxyUser} =
-      { uid = config.ids.uids.nntp-proxy;
-        description = "NNTP-Proxy daemon user";
-      };
+    users.users.nntp-proxy = {
+      isSystemUser = true;
+      group = "nntp-proxy";
+      description = "NNTP-Proxy daemon user";
+    };
+    users.groups.nntp-proxy = {};
 
     systemd.services.nntp-proxy = {
       description = "NNTP proxy";
       after = [ "network.target" "nss-lookup.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig = { User="${proxyUser}"; };
+      serviceConfig = { User="nntp-proxy"; };
       serviceConfig.ExecStart = "${nntp-proxy}/bin/nntp-proxy ${confFile}";
       preStart = ''
         if [ ! \( -f ${cfg.sslCert} -a -f ${cfg.sslKey} \) ]; then
diff --git a/nixos/modules/services/networking/nomad.nix b/nixos/modules/services/networking/nomad.nix
index 48689f1195c5b..43333af5e2fea 100644
--- a/nixos/modules/services/networking/nomad.nix
+++ b/nixos/modules/services/networking/nomad.nix
@@ -13,7 +13,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.nomad;
-        defaultText = "pkgs.nomad";
+        defaultText = literalExpression "pkgs.nomad";
         description = ''
           The package used for the Nomad agent and CLI.
         '';
@@ -25,7 +25,7 @@ in
         description = ''
           Extra packages to add to <envar>PATH</envar> for the Nomad agent process.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           with pkgs; [ cni-plugins ]
         '';
       };
@@ -51,18 +51,30 @@ in
 
       extraSettingsPaths = mkOption {
         type = types.listOf types.path;
-        default = [];
+        default = [ ];
         description = ''
           Additional settings paths used to configure nomad. These can be files or directories.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           [ "/etc/nomad-mutable.json" "/run/keys/nomad-with-secrets.json" "/etc/nomad/config.d" ]
         '';
       };
 
+      extraSettingsPlugins = mkOption {
+        type = types.listOf (types.either types.package types.path);
+        default = [ ];
+        description = ''
+          Additional plugins dir used to configure nomad.
+        '';
+        example = literalExpression ''
+          [ "<pluginDir>" "pkgs.<plugins-name>"]
+        '';
+      };
+
+
       settings = mkOption {
         type = format.type;
-        default = {};
+        default = { };
         description = ''
           Configuration for Nomad. See the <link xlink:href="https://www.nomadproject.io/docs/configuration">documentation</link>
           for supported values.
@@ -81,7 +93,7 @@ in
           the <literal>DynamicUser</literal> feature of systemd which directly
           manages and operates on <literal>StateDirectory</literal>.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             # A minimal config example:
             server = {
@@ -128,7 +140,8 @@ in
           DynamicUser = cfg.dropPrivileges;
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           ExecStart = "${cfg.package}/bin/nomad agent -config=/etc/nomad.json" +
-            concatMapStrings (path: " -config=${path}") cfg.extraSettingsPaths;
+            concatMapStrings (path: " -config=${path}") cfg.extraSettingsPaths +
+            concatMapStrings (path: " -plugin-dir=${path}/bin") cfg.extraSettingsPlugins;
           KillMode = "process";
           KillSignal = "SIGINT";
           LimitNOFILE = 65536;
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index 2ac0a8c7922ed..893995165b9e4 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -260,7 +260,6 @@ let
       data = mkOption {
         type = types.lines;
         default = "";
-        example = "";
         description = ''
           The actual zone data. This is the content of your zone file.
           Use imports or pkgs.lib.readFile if you don't want this data in your config file.
@@ -397,7 +396,6 @@ let
       requestXFR = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = [];
         description = ''
           Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code>
         '';
@@ -726,7 +724,7 @@ in
         };
       });
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         { "tsig.example.org" = {
             algorithm = "hmac-md5";
             keyFile = "/path/to/my/key";
@@ -861,7 +859,7 @@ in
     zones = mkOption {
       type = types.attrsOf zoneOptions;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         { "serverGroup1" = {
             provideXFR = [ "10.1.2.3 NOKEY" ];
             children = {
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index ed61c178c685e..d414936a2c2be 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -44,7 +44,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.chrony;
-        defaultText = "pkgs.chrony";
+        defaultText = literalExpression "pkgs.chrony";
         description = ''
           Which chrony package to use.
         '';
diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix
index 861b0db01a489..ce4802ce02450 100644
--- a/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/nixos/modules/services/networking/ntp/ntpd.nix
@@ -10,8 +10,6 @@ let
 
   stateDir = "/var/lib/ntp";
 
-  ntpUser = "ntp";
-
   configFile = pkgs.writeText "ntp.conf" ''
     driftfile ${stateDir}/ntp.drift
 
@@ -27,7 +25,7 @@ let
     ${cfg.extraConfig}
   '';
 
-  ntpFlags = "-c ${configFile} -u ${ntpUser}:nogroup ${toString cfg.extraFlags}";
+  ntpFlags = "-c ${configFile} -u ntp:ntp ${toString cfg.extraFlags}";
 
 in
 
@@ -99,7 +97,7 @@ in
       extraFlags = mkOption {
         type = types.listOf types.str;
         description = "Extra flags passed to the ntpd command.";
-        example = literalExample ''[ "--interface=eth0" ]'';
+        example = literalExpression ''[ "--interface=eth0" ]'';
         default = [];
       };
 
@@ -119,11 +117,13 @@ in
 
     systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; };
 
-    users.users.${ntpUser} =
-      { uid = config.ids.uids.ntp;
+    users.users.ntp =
+      { isSystemUser = true;
+        group = "ntp";
         description = "NTP daemon user";
         home = stateDir;
       };
+    users.groups.ntp = {};
 
     systemd.services.ntpd =
       { description = "NTP Daemon";
@@ -135,7 +135,7 @@ in
         preStart =
           ''
             mkdir -m 0755 -p ${stateDir}
-            chown ${ntpUser} ${stateDir}
+            chown ntp ${stateDir}
           '';
 
         serviceConfig = {
diff --git a/nixos/modules/services/networking/ntp/openntpd.nix b/nixos/modules/services/networking/ntp/openntpd.nix
index 67a04d48d3083..9f3892e3b538e 100644
--- a/nixos/modules/services/networking/ntp/openntpd.nix
+++ b/nixos/modules/services/networking/ntp/openntpd.nix
@@ -61,10 +61,12 @@ in
     environment.etc."ntpd.conf".text = configFile;
 
     users.users.ntp = {
-      uid = config.ids.uids.ntp;
+      isSystemUser = true;
+      group = "ntp";
       description = "OpenNTP daemon user";
       home = "/var/empty";
     };
+    users.groups.ntp = {};
 
     systemd.services.openntpd = {
       description = "OpenNTP Server";
diff --git a/nixos/modules/services/networking/ofono.nix b/nixos/modules/services/networking/ofono.nix
index 40ef9433de0fb..460b06443c412 100644
--- a/nixos/modules/services/networking/ofono.nix
+++ b/nixos/modules/services/networking/ofono.nix
@@ -24,7 +24,7 @@ in
       plugins = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.modem-manager-gui ]";
+        example = literalExpression "[ pkgs.modem-manager-gui ]";
         description = ''
           The list of plugins to install.
         '';
diff --git a/nixos/modules/services/networking/onedrive.nix b/nixos/modules/services/networking/onedrive.nix
index c52f920bae25e..0256a6a411152 100644
--- a/nixos/modules/services/networking/onedrive.nix
+++ b/nixos/modules/services/networking/onedrive.nix
@@ -35,8 +35,7 @@ in {
      package = lib.mkOption {
        type = lib.types.package;
        default = pkgs.onedrive;
-       defaultText = "pkgs.onedrive";
-       example = lib.literalExample "pkgs.onedrive";
+       defaultText = lib.literalExpression "pkgs.onedrive";
        description = ''
          OneDrive package to use.
        '';
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index b4c2c944b6e60..cf3f79fc578fa 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -84,7 +84,7 @@ in
     services.openvpn.servers = mkOption {
       default = {};
 
-      example = literalExample ''
+      example = literalExpression ''
         {
           server = {
             config = '''
diff --git a/nixos/modules/services/networking/ostinato.nix b/nixos/modules/services/networking/ostinato.nix
index 5e8cce5b89aaf..4da11984b9fc7 100644
--- a/nixos/modules/services/networking/ostinato.nix
+++ b/nixos/modules/services/networking/ostinato.nix
@@ -65,7 +65,7 @@ in
         include = mkOption {
           type = types.listOf types.str;
           default = [];
-          example = ''[ "eth*" "lo*" ]'';
+          example = [ "eth*" "lo*" ];
           description = ''
             For a port to pass the filter and appear on the port list managed
             by drone, it be allowed by this include list.
@@ -74,7 +74,7 @@ in
         exclude = mkOption {
           type = types.listOf types.str;
           default = [];
-          example = ''[ "usbmon*" "eth0" ]'';
+          example = [ "usbmon*" "eth0" ];
           description = ''
             A list of ports does not appear on the port list managed by drone.
           '';
diff --git a/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix
index a326eccfd65d1..0579d314a9ba6 100644
--- a/nixos/modules/services/networking/pdns-recursor.nix
+++ b/nixos/modules/services/networking/pdns-recursor.nix
@@ -127,7 +127,7 @@ in {
     settings = mkOption {
       type = configType;
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           loglevel = 8;
           log-common-errors = true;
diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix
index bd75083a4a781..2f32faf387ca6 100644
--- a/nixos/modules/services/networking/pleroma.nix
+++ b/nixos/modules/services/networking/pleroma.nix
@@ -9,6 +9,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.pleroma;
+        defaultText = literalExpression "pkgs.pleroma";
         description = "Pleroma package to use.";
       };
 
@@ -74,7 +75,7 @@ in {
       users."${cfg.user}" = {
         description = "Pleroma user";
         home = cfg.stateDir;
-        extraGroups = [ cfg.group ];
+        group = cfg.group;
         isSystemUser = true;
       };
       groups."${cfg.group}" = {};
diff --git a/nixos/modules/services/networking/pleroma.xml b/nixos/modules/services/networking/pleroma.xml
index 9ab0be3d947c8..ad0a481af28b5 100644
--- a/nixos/modules/services/networking/pleroma.xml
+++ b/nixos/modules/services/networking/pleroma.xml
@@ -4,129 +4,185 @@
          version="5.0"
          xml:id="module-services-pleroma">
  <title>Pleroma</title>
- <para><link xlink:href="https://pleroma.social/">Pleroma</link> is a lightweight activity pub server.</para>
- <section xml:id="module-services-pleroma-getting-started">
-   <title>Quick Start</title>
-   <para>To get quickly started, you can use this sample NixOS configuration and adapt it to your use case.</para>
-   <para><programlisting>
-    {
-      security.acme = {
-        email = "root@tld";
-        acceptTerms = true;
-        certs = {
-          "social.tld.com" = {
-            webroot = "/var/www/social.tld.com";
-            email = "root@tld";
-            group = "nginx";
-          };
-        };
-      };
-      services = {
-        pleroma = {
-          enable = true;
-          secretConfigFile = "/var/lib/pleroma/secrets.exs";
-          configs = [
-          ''
-            import Config
-
-            config :pleroma, Pleroma.Web.Endpoint,
-            url: [host: "social.tld.com", scheme: "https", port: 443],
-            http: [ip: {127, 0, 0, 1}, port: 4000]
-
-            config :pleroma, :instance,
-            name: "NixOS test pleroma server",
-            email: "pleroma@social.tld.com",
-            notify_email: "pleroma@social.tld.com",
-            limit: 5000,
-            registrations_open: true
-
-            config :pleroma, :media_proxy,
-            enabled: false,
-            redirect_on_failure: true
-            #base_url: "https://cache.pleroma.social"
-
-            config :pleroma, Pleroma.Repo,
-            adapter: Ecto.Adapters.Postgres,
-            username: "pleroma",
-            password: "${test-db-passwd}",
-            database: "pleroma",
-            hostname: "localhost",
-            pool_size: 10,
-            prepare: :named,
-            parameters: [
-                plan_cache_mode: "force_custom_plan"
-            ]
-
-            config :pleroma, :database, rum_enabled: false
-            config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
-            config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
-            config :pleroma, configurable_from_database: false
-          ''
-          ];
-        };
-        postgresql = {
-          enable = true;
-          package = pkgs.postgresql_12;
-        };
-        nginx = {
-          enable = true;
-          addSSL = true;
-          sslCertificate = "/var/lib/acme/social.tld.com/fullchain.pem";
-          sslCertificateKey = "/var/lib/acme/social.tld.com/key.pem";
-          root = "/var/www/social.tld.com";
-          # ACME endpoint
-          locations."/.well-known/acme-challenge" = {
-              root = "/var/www/social.tld.com/";
-          };
-          virtualHosts."social.tld.com" = {
-            addSSL = true;
-            locations."/" = {
-              proxyPass = "http://127.0.0.1:4000";
-              extraConfig = ''
-                add_header 'Access-Control-Allow-Origin' '*' always;
-                add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
-                add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
-                add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
-                if ($request_method = OPTIONS) {
-                    return 204;
-                }
-                add_header X-XSS-Protection "1; mode=block";
-                add_header X-Permitted-Cross-Domain-Policies none;
-                add_header X-Frame-Options DENY;
-                add_header X-Content-Type-Options nosniff;
-                add_header Referrer-Policy same-origin;
-                add_header X-Download-Options noopen;
-                proxy_http_version 1.1;
-                proxy_set_header Upgrade $http_upgrade;
-                proxy_set_header Connection "upgrade";
-                proxy_set_header Host $host;
-                client_max_body_size 16m;
-              '';
-            };
-          };
-        };
-      };
-    };
-   </programlisting></para>
-   <para>Note that you'll need to seed your database and upload your pleroma secrets to the path pointed by <literal>config.pleroma.secretConfigFile</literal>. You can find more informations about how to do that in the <link linkend="module-services-pleroma-generate-config">next</link> section.</para>
- </section>
+ <para>
+  <link xlink:href="https://pleroma.social/">Pleroma</link> is a lightweight activity pub server.</para>
  <section xml:id="module-services-pleroma-generate-config">
-   <title>Generating the Pleroma Config and Seed the Database</title>
-
-   <para>Before using this service, you'll need to generate your
-server configuration and its associated database seed. The
-<literal>pleroma_ctl</literal> CLI utility can help you with that. You
-can start with <literal>pleroma_ctl instance gen --output config.exs
---output-psql setup.psql</literal>, this will prompt you some
-questions and will generate both your config file and database initial
-migration. </para>
-<para>For more details about this configuration format, please have a look at the <link xlink:href="https://docs-develop.pleroma.social/backend/configuration/cheatsheet/">upstream documentation</link>.</para>
-<para>To seed your database, you can use the <literal>setup.psql</literal> file you just generated by running
+  <title>Generating the Pleroma config</title>
+  <para>The <literal>pleroma_ctl</literal> CLI utility will prompt you some questions and it will generate an initial config file. This is an example of usage
+<programlisting>
+<prompt>$ </prompt>mkdir tmp-pleroma
+<prompt>$ </prompt>cd tmp-pleroma
+<prompt>$ </prompt>nix-shell -p pleroma-otp
+<prompt>$ </prompt>pleroma_ctl instance gen --output config.exs --output-psql setup.psql
+</programlisting>
+  </para>
+  <para>The <literal>config.exs</literal> file can be further customized following the instructions on the <link xlink:href="https://docs-develop.pleroma.social/backend/configuration/cheatsheet/">upstream documentation</link>. Many refinements can be applied also after the service is running.</para>
+ </section>
+ <section xml:id="module-services-pleroma-initialize-db">
+  <title>Initializing the database</title>
+  <para>First, the Postgresql service must be enabled in the NixOS configuration
+<programlisting>
+services.postgresql = {
+  enable = true;
+  package = pkgs.postgresql_13;
+};
+</programlisting>
+and activated with the usual
+<programlisting>
+<prompt>$ </prompt>nixos-rebuild switch
+</programlisting>
+  </para>
+  <para>Then you can create and seed the database, using the <literal>setup.psql</literal> file that you generated in the previous section, by running
+<programlisting>
+<prompt>$ </prompt>sudo -u postgres psql -f setup.psql
+</programlisting>
+  </para>
+ </section>
+ <section xml:id="module-services-pleroma-enable">
+  <title>Enabling the Pleroma service locally</title>
+  <para>In this section we will enable the Pleroma service only locally, so its configurations can be improved incrementally.</para>
+  <para>This is an example of configuration, where <link linkend="opt-services.pleroma.configs">services.pleroma.configs</link> option contains the content of the file <literal>config.exs</literal>, generated <link linkend="module-services-pleroma-generate-config">in the first section</link>, but with the secrets (database password, endpoint secret key, salts, etc.) removed. Removing secrets is important, because otherwise they will be stored publicly in the Nix store.
+<programlisting>
+services.pleroma = {
+  enable = true;
+  secretConfigFile = "/var/lib/pleroma/secrets.exs";
+  configs = [
+    ''
+    import Config
+
+    config :pleroma, Pleroma.Web.Endpoint,
+      url: [host: "pleroma.example.net", scheme: "https", port: 443],
+      http: [ip: {127, 0, 0, 1}, port: 4000]
+
+    config :pleroma, :instance,
+      name: "Test",
+      email: "admin@example.net",
+      notify_email: "admin@example.net",
+      limit: 5000,
+      registrations_open: true
+
+    config :pleroma, :media_proxy,
+      enabled: false,
+      redirect_on_failure: true
+
+    config :pleroma, Pleroma.Repo,
+      adapter: Ecto.Adapters.Postgres,
+      username: "pleroma",
+      database: "pleroma",
+      hostname: "localhost"
+
+    # Configure web push notifications
+    config :web_push_encryption, :vapid_details,
+      subject: "mailto:admin@example.net"
+
+    # ... TO CONTINUE ...
+    ''
+  ];
+};
+</programlisting>
+  </para>
+  <para>Secrets must be moved into a file pointed by <link linkend="opt-services.pleroma.secretConfigFile">services.pleroma.secretConfigFile</link>, in our case <literal>/var/lib/pleroma/secrets.exs</literal>. This file can be created copying the previously generated <literal>config.exs</literal> file and then removing all the settings, except the secrets. This is an example
+<programlisting>
+# Pleroma instance passwords
+
+import Config
+
+config :pleroma, Pleroma.Web.Endpoint,
+   secret_key_base: "&lt;the secret generated by pleroma_ctl&gt;",
+   signing_salt: "&lt;the secret generated by pleroma_ctl&gt;"
+
+config :pleroma, Pleroma.Repo,
+  password: "&lt;the secret generated by pleroma_ctl&gt;"
+
+# Configure web push notifications
+config :web_push_encryption, :vapid_details,
+  public_key: "&lt;the secret generated by pleroma_ctl&gt;",
+  private_key: "&lt;the secret generated by pleroma_ctl&gt;"
+
+# ... TO CONTINUE ...
+</programlisting>
+  Note that the lines of the same configuration group are comma separated (i.e. all the lines end with a comma, except the last one), so when the lines with passwords are added or removed, commas must be adjusted accordingly.</para>
+
+  <para>The service can be enabled with the usual
+<programlisting>
+<prompt>$ </prompt>nixos-rebuild switch
+</programlisting>
+  </para>
+  <para>The service is accessible only from the local <literal>127.0.0.1:4000</literal> port. It can be tested using a port forwarding like this
+<programlisting>
+<prompt>$ </prompt>ssh -L 4000:localhost:4000 myuser@example.net
+</programlisting>
+and then accessing <link xlink:href="http://localhost:4000">http://localhost:4000</link> from a web browser.</para>
+ </section>
+ <section xml:id="module-services-pleroma-admin-user">
+  <title>Creating the admin user</title>
+  <para>After Pleroma service is running, all <link xlink:href="https://docs-develop.pleroma.social/">Pleroma administration utilities</link> can be used. In particular an admin user can be created with
+<programlisting>
+<prompt>$ </prompt>pleroma_ctl user new &lt;nickname&gt; &lt;email&gt;  --admin --moderator --password &lt;password&gt;
+</programlisting>
+  </para>
+ </section>
+ <section xml:id="module-services-pleroma-nginx">
+  <title>Configuring Nginx</title>
+  <para>In this configuration, Pleroma is listening only on the local port 4000. Nginx can be configured as a Reverse Proxy, for forwarding requests from public ports to the Pleroma service. This is an example of configuration, using
+<link xlink:href="https://letsencrypt.org/">Let's Encrypt</link> for the TLS certificates
 <programlisting>
-    sudo -u postgres psql -f setup.psql
-</programlisting></para>
-   <para>In regard of the pleroma service configuration you also just generated, you'll need to split it in two parts. The "public" part, which do not contain any secrets and thus can be safely stored in the Nix store and its "private" counterpart containing some secrets (database password, endpoint secret key, salts, etc.).</para>
+security.acme = {
+  email = "root@example.net";
+  acceptTerms = true;
+};
 
-   <para>The public part will live in your NixOS machine configuration in the <link linkend="opt-services.pleroma.configs">services.pleroma.configs</link> option. However, it's up to you to upload the secret pleroma configuration to the path pointed by <link linkend="opt-services.pleroma.secretConfigFile">services.pleroma.secretConfigFile</link>. You can do that manually or rely on a third party tool such as <link xlink:href="https://github.com/DBCDK/morph">Morph</link> or <link xlink:href="https://github.com/NixOS/nixops">NixOps</link>.</para>
+services.nginx = {
+  enable = true;
+  addSSL = true;
+
+  recommendedTlsSettings = true;
+  recommendedOptimisation = true;
+  recommendedGzipSettings = true;
+
+  recommendedProxySettings = false;
+  # NOTE: if enabled, the NixOS proxy optimizations will override the Pleroma
+  # specific settings, and they will enter in conflict.
+
+  virtualHosts = {
+    "pleroma.example.net" = {
+      http2 = true;
+      enableACME = true;
+      forceSSL = true;
+
+      locations."/" = {
+        proxyPass = "http://127.0.0.1:4000";
+
+        extraConfig = ''
+          etag on;
+          gzip on;
+
+          add_header 'Access-Control-Allow-Origin' '*' always;
+          add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
+          add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
+          add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
+          if ($request_method = OPTIONS) {
+            return 204;
+          }
+          add_header X-XSS-Protection "1; mode=block";
+          add_header X-Permitted-Cross-Domain-Policies none;
+          add_header X-Frame-Options DENY;
+          add_header X-Content-Type-Options nosniff;
+          add_header Referrer-Policy same-origin;
+          add_header X-Download-Options noopen;
+          proxy_http_version 1.1;
+          proxy_set_header Upgrade $http_upgrade;
+          proxy_set_header Connection "upgrade";
+          proxy_set_header Host $host;
+
+          client_max_body_size 16m;
+          # NOTE: increase if users need to upload very big files
+        '';
+      };
+    };
+  };
+};
+</programlisting>
+  </para>
  </section>
 </chapter>
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
index 37f44f07ac46b..d1ed25b0238f4 100644
--- a/nixos/modules/services/networking/pppd.nix
+++ b/nixos/modules/services/networking/pppd.nix
@@ -16,7 +16,7 @@ in
 
       package = mkOption {
         default = pkgs.ppp;
-        defaultText = "pkgs.ppp";
+        defaultText = literalExpression "pkgs.ppp";
         type = types.package;
         description = "pppd package to use.";
       };
diff --git a/nixos/modules/services/networking/privoxy.nix b/nixos/modules/services/networking/privoxy.nix
index df818baa465d9..7bc964d5f34ae 100644
--- a/nixos/modules/services/networking/privoxy.nix
+++ b/nixos/modules/services/networking/privoxy.nix
@@ -164,7 +164,7 @@ in
         };
       };
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         { # Listen on IPv6 only
           listen-address = "[::]:8118";
 
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index e7a7aa700be6e..42596ccfefd97 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -500,8 +500,8 @@ in
         type = types.package;
         description = "Prosody package to use";
         default = pkgs.prosody;
-        defaultText = "pkgs.prosody";
-        example = literalExample ''
+        defaultText = literalExpression "pkgs.prosody";
+        example = literalExpression ''
           pkgs.prosody.override {
             withExtraLibs = [ pkgs.luaPackages.lpty ];
             withCommunityModules = [ "auth_external" ];
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index bfbd3b46ab4d9..22940ef7a13a8 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -37,11 +37,10 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.quasselDaemon;
-        defaultText = "pkgs.quasselDaemon";
+        defaultText = literalExpression "pkgs.quasselDaemon";
         description = ''
           The package of the quassel daemon.
         '';
-        example = literalExample "pkgs.quasselDaemon";
       };
 
       interfaces = mkOption {
diff --git a/nixos/modules/services/networking/quorum.nix b/nixos/modules/services/networking/quorum.nix
index 2f612c9db6861..50148dc314da0 100644
--- a/nixos/modules/services/networking/quorum.nix
+++ b/nixos/modules/services/networking/quorum.nix
@@ -1,7 +1,7 @@
 { config, pkgs, lib, ... }:
 let
 
-  inherit (lib) mkEnableOption mkIf mkOption literalExample types optionalString;
+  inherit (lib) mkEnableOption mkIf mkOption literalExpression types optionalString;
 
   cfg = config.services.quorum;
   dataDir = "/var/lib/quorum";
@@ -130,7 +130,7 @@ in {
       genesis = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        example = literalExample '' {
+        example = literalExpression '' {
           alloc = {
             a47385db68718bdcbddc2d2bb7c54018066ec111 = {
               balance = "1000000000000000000000000000";
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index 8c632c319d3c0..c121008d5294d 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -33,7 +33,7 @@ in {
       # warnings about incompatible configuration and storage formats.
       type = with types; nullOr package // { inherit (package) description; };
       default = null;
-      defaultText = "pkgs.radicale";
+      defaultText = literalExpression "pkgs.radicale";
     };
 
     config = mkOption {
@@ -55,7 +55,7 @@ in {
         <link xlink:href="https://radicale.org/3.0.html#documentation/configuration" />.
         This option is mutually exclusive with <option>config</option>.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         server = {
           hosts = [ "0.0.0.0:5232" "[::]:5232" ];
         };
@@ -80,7 +80,7 @@ in {
         <option>settings.rights.file</option> to approriate values.
       '';
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         root = {
           user = ".+";
           collection = "";
@@ -140,9 +140,12 @@ in {
 
     environment.systemPackages = [ pkg ];
 
-    users.users.radicale.uid = config.ids.uids.radicale;
+    users.users.radicale = {
+      isSystemUser = true;
+      group = "radicale";
+    };
 
-    users.groups.radicale.gid = config.ids.gids.radicale;
+    users.groups.radicale = {};
 
     systemd.services.radicale = {
       description = "A Simple Calendar and Contact Server";
diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix
index 53fac4b7b72dc..6e8db55bbf0d1 100644
--- a/nixos/modules/services/networking/radvd.nix
+++ b/nixos/modules/services/networking/radvd.nix
@@ -55,9 +55,12 @@ in
   config = mkIf cfg.enable {
 
     users.users.radvd =
-      { uid = config.ids.uids.radvd;
+      {
+        isSystemUser = true;
+        group = "radvd";
         description = "Router Advertisement Daemon User";
       };
+    users.groups.radvd = {};
 
     systemd.services.radvd =
       { description = "IPv6 Router Advertisement Daemon";
diff --git a/nixos/modules/services/networking/rdnssd.nix b/nixos/modules/services/networking/rdnssd.nix
index 469504c43172f..fd04bb8108f0c 100644
--- a/nixos/modules/services/networking/rdnssd.nix
+++ b/nixos/modules/services/networking/rdnssd.nix
@@ -72,8 +72,10 @@ in
 
     users.users.rdnssd = {
       description = "RDNSSD Daemon User";
-      uid = config.ids.uids.rdnssd;
+      isSystemUser = true;
+      group = "rdnssd";
     };
+    users.groups.rdnssd = {};
 
   };
 
diff --git a/nixos/modules/services/networking/sabnzbd.nix b/nixos/modules/services/networking/sabnzbd.nix
index 43566dfd25c5f..54eeba1a9ec17 100644
--- a/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixos/modules/services/networking/sabnzbd.nix
@@ -17,6 +17,13 @@ in
     services.sabnzbd = {
       enable = mkEnableOption "the sabnzbd server";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.sabnzbd;
+        defaultText = "pkgs.sabnzbd";
+        description = "The sabnzbd executable package run by the service.";
+      };
+
       configFile = mkOption {
         type = types.path;
         default = "/var/lib/sabnzbd/sabnzbd.ini";
@@ -63,7 +70,7 @@ in
           GuessMainPID = "no";
           User = "${cfg.user}";
           Group = "${cfg.group}";
-          ExecStart = "${sabnzbd}/bin/sabnzbd -d -f ${cfg.configFile}";
+          ExecStart = "${lib.getBin cfg.package}/bin/sabnzbd -d -f ${cfg.configFile}";
         };
     };
   };
diff --git a/nixos/modules/services/networking/seafile.nix b/nixos/modules/services/networking/seafile.nix
new file mode 100644
index 0000000000000..856797b6b0209
--- /dev/null
+++ b/nixos/modules/services/networking/seafile.nix
@@ -0,0 +1,290 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  python = pkgs.python3Packages.python;
+  cfg = config.services.seafile;
+  settingsFormat = pkgs.formats.ini { };
+
+  ccnetConf = settingsFormat.generate "ccnet.conf" cfg.ccnetSettings;
+
+  seafileConf = settingsFormat.generate "seafile.conf" cfg.seafileSettings;
+
+  seahubSettings = pkgs.writeText "seahub_settings.py" ''
+    FILE_SERVER_ROOT = '${cfg.ccnetSettings.General.SERVICE_URL}/seafhttp'
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.sqlite3',
+            'NAME': '${seahubDir}/seahub.db',
+        }
+    }
+    MEDIA_ROOT = '${seahubDir}/media/'
+    THUMBNAIL_ROOT = '${seahubDir}/thumbnail/'
+
+    with open('${seafRoot}/.seahubSecret') as f:
+        SECRET_KEY = f.readline().rstrip()
+
+    ${cfg.seahubExtraConf}
+  '';
+
+  seafRoot = "/var/lib/seafile"; # hardcode it due to dynamicuser
+  ccnetDir = "${seafRoot}/ccnet";
+  dataDir = "${seafRoot}/data";
+  seahubDir = "${seafRoot}/seahub";
+
+in {
+
+  ###### Interface
+
+  options.services.seafile = {
+    enable = mkEnableOption "Seafile server";
+
+    ccnetSettings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          General = {
+            SERVICE_URL = mkOption {
+              type = types.str;
+              example = "https://www.example.com";
+              description = ''
+                Seahub public URL.
+              '';
+            };
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Configuration for ccnet, see
+        <link xlink:href="https://manual.seafile.com/config/ccnet-conf/"/>
+        for supported values.
+      '';
+    };
+
+    seafileSettings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          fileserver = {
+            port = mkOption {
+              type = types.port;
+              default = 8082;
+              description = ''
+                The tcp port used by seafile fileserver.
+              '';
+            };
+            host = mkOption {
+              type = types.str;
+              default = "127.0.0.1";
+              example = "0.0.0.0";
+              description = ''
+                The binding address used by seafile fileserver.
+              '';
+            };
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Configuration for seafile-server, see
+        <link xlink:href="https://manual.seafile.com/config/seafile-conf/"/>
+        for supported values.
+      '';
+    };
+
+    workers = mkOption {
+      type = types.int;
+      default = 4;
+      example = 10;
+      description = ''
+        The number of gunicorn worker processes for handling requests.
+      '';
+    };
+
+    adminEmail = mkOption {
+      example = "john@example.com";
+      type = types.str;
+      description = ''
+        Seafile Seahub Admin Account Email.
+      '';
+    };
+
+    initialAdminPassword = mkOption {
+      example = "someStrongPass";
+      type = types.str;
+      description = ''
+        Seafile Seahub Admin Account initial password.
+        Should be change via Seahub web front-end.
+      '';
+    };
+
+    seafilePackage = mkOption {
+      type = types.package;
+      description = "Which package to use for the seafile server.";
+      default = pkgs.seafile-server;
+    };
+
+    seahubExtraConf = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Extra config to append to `seahub_settings.py` file.
+        Refer to <link xlink:href="https://manual.seafile.com/config/seahub_settings_py/" />
+        for all available options.
+      '';
+    };
+  };
+
+  ###### Implementation
+
+  config = mkIf cfg.enable {
+
+    environment.etc."seafile/ccnet.conf".source = ccnetConf;
+    environment.etc."seafile/seafile.conf".source = seafileConf;
+    environment.etc."seafile/seahub_settings.py".source = seahubSettings;
+
+    systemd.targets.seafile = {
+      wantedBy = [ "multi-user.target" ];
+      description = "Seafile components";
+    };
+
+    systemd.services = let
+      securityOptions = {
+        ProtectHome = true;
+        PrivateUsers = true;
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectProc = "invisible";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        MemoryDenyWriteExecute = true;
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" ];
+      };
+    in {
+      seaf-server = {
+        description = "Seafile server";
+        partOf = [ "seafile.target" ];
+        after = [ "network.target" ];
+        wantedBy = [ "seafile.target" ];
+        restartTriggers = [ ccnetConf seafileConf ];
+        serviceConfig = securityOptions // {
+          User = "seafile";
+          Group = "seafile";
+          DynamicUser = true;
+          StateDirectory = "seafile";
+          RuntimeDirectory = "seafile";
+          LogsDirectory = "seafile";
+          ConfigurationDirectory = "seafile";
+          ExecStart = ''
+            ${cfg.seafilePackage}/bin/seaf-server \
+            --foreground \
+            -F /etc/seafile \
+            -c ${ccnetDir} \
+            -d ${dataDir} \
+            -l /var/log/seafile/server.log \
+            -P /run/seafile/server.pid \
+            -p /run/seafile
+          '';
+        };
+        preStart = ''
+          if [ ! -f "${seafRoot}/server-setup" ]; then
+              mkdir -p ${dataDir}/library-template
+              mkdir -p ${ccnetDir}/{GroupMgr,misc,OrgMgr,PeerMgr}
+              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/GroupMgr/groupmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/groupmgr.sql"
+              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/misc/config.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/config.sql"
+              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/OrgMgr/orgmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/org.sql"
+              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/PeerMgr/usermgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/user.sql"
+              ${pkgs.sqlite}/bin/sqlite3 ${dataDir}/seafile.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/seafile.sql"
+              echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup
+          fi
+          # checking for upgrades and handling them
+          # WARNING: needs to be extended to actually handle major version migrations
+          installedMajor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f1)
+          installedMinor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f2)
+          pkgMajor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f1)
+          pkgMinor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f2)
+          if [ $installedMajor != $pkgMajor ] || [ $installedMinor != $pkgMinor ]; then
+              echo "Unsupported upgrade" >&2
+              exit 1
+          fi
+        '';
+      };
+
+      seahub = let
+        penv = (pkgs.python3.withPackages (ps: with ps; [ gunicorn seahub ]));
+      in {
+        description = "Seafile Server Web Frontend";
+        wantedBy = [ "seafile.target" ];
+        partOf = [ "seafile.target" ];
+        after = [ "network.target" "seaf-server.service" ];
+        requires = [ "seaf-server.service" ];
+        restartTriggers = [ seahubSettings ];
+        environment = {
+          PYTHONPATH =
+            "${pkgs.python3Packages.seahub}/thirdpart:${pkgs.python3Packages.seahub}:${penv}/${python.sitePackages}";
+          DJANGO_SETTINGS_MODULE = "seahub.settings";
+          CCNET_CONF_DIR = ccnetDir;
+          SEAFILE_CONF_DIR = dataDir;
+          SEAFILE_CENTRAL_CONF_DIR = "/etc/seafile";
+          SEAFILE_RPC_PIPE_PATH = "/run/seafile";
+          SEAHUB_LOG_DIR = "/var/log/seafile";
+        };
+        serviceConfig = securityOptions // {
+          User = "seafile";
+          Group = "seafile";
+          DynamicUser = true;
+          RuntimeDirectory = "seahub";
+          StateDirectory = "seafile";
+          LogsDirectory = "seafile";
+          ConfigurationDirectory = "seafile";
+          ExecStart = ''
+            ${penv}/bin/gunicorn seahub.wsgi:application \
+            --name seahub \
+            --workers ${toString cfg.workers} \
+            --log-level=info \
+            --preload \
+            --timeout=1200 \
+            --limit-request-line=8190 \
+            --bind unix:/run/seahub/gunicorn.sock
+          '';
+        };
+        preStart = ''
+          mkdir -p ${seahubDir}/media
+          # Link all media except avatars
+          for m in `find ${pkgs.python3Packages.seahub}/media/ -maxdepth 1 -not -name "avatars"`; do
+            ln -sf $m ${seahubDir}/media/
+          done
+          if [ ! -e "${seafRoot}/.seahubSecret" ]; then
+              ${penv}/bin/python ${pkgs.python3Packages.seahub}/tools/secret_key_generator.py > ${seafRoot}/.seahubSecret
+              chmod 400 ${seafRoot}/.seahubSecret
+          fi
+          if [ ! -f "${seafRoot}/seahub-setup" ]; then
+              # avatars directory should be writable
+              install -D -t ${seahubDir}/media/avatars/ ${pkgs.python3Packages.seahub}/media/avatars/default.png
+              install -D -t ${seahubDir}/media/avatars/groups ${pkgs.python3Packages.seahub}/media/avatars/groups/default.png
+              # init database
+              ${pkgs.python3Packages.seahub}/manage.py migrate
+              # create admin account
+              ${pkgs.expect}/bin/expect -c 'spawn ${pkgs.python3Packages.seahub}/manage.py createsuperuser --email=${cfg.adminEmail}; expect "Password: "; send "${cfg.initialAdminPassword}\r"; expect "Password (again): "; send "${cfg.initialAdminPassword}\r"; expect "Superuser created successfully."'
+              echo "${pkgs.python3Packages.seahub.version}-sqlite" > "${seafRoot}/seahub-setup"
+          fi
+          if [ $(cat "${seafRoot}/seahub-setup" | cut -d"-" -f1) != "${pkgs.python3Packages.seahub.version}" ]; then
+              # update database
+              ${pkgs.python3Packages.seahub}/manage.py migrate
+              echo "${pkgs.python3Packages.seahub.version}-sqlite" > "${seafRoot}/seahub-setup"
+          fi
+        '';
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index 04f7d7e31f467..9fb06af7442e4 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -68,7 +68,7 @@ in
       settings = mkOption {
         type = types.attrsOf settingType;
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           { server.port = 8080;
             server.bind_address = "0.0.0.0";
             server.secret_key = "@SEARX_SECRET_KEY@";
@@ -116,7 +116,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.searx;
-        defaultText = "pkgs.searx";
+        defaultText = literalExpression "pkgs.searx";
         description = "searx package to use.";
       };
 
@@ -138,7 +138,7 @@ in
       uwsgiConfig = mkOption {
         type = options.services.uwsgi.instance.type;
         default = { http = ":8080"; };
-        example = literalExample ''
+        example = literalExpression ''
           {
             disable-logging = true;
             http = ":8080";                   # serve via HTTP...
diff --git a/nixos/modules/services/networking/shadowsocks.nix b/nixos/modules/services/networking/shadowsocks.nix
index d2541f9a6dffc..7bea269a9ed0b 100644
--- a/nixos/modules/services/networking/shadowsocks.nix
+++ b/nixos/modules/services/networking/shadowsocks.nix
@@ -98,7 +98,7 @@ in
       plugin = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "\${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
+        example = literalExpression ''"''${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin"'';
         description = ''
           SIP003 plugin for shadowsocks
         '';
@@ -116,11 +116,9 @@ in
       extraConfig = mkOption {
         type = types.attrs;
         default = {};
-        example = ''
-          {
-            nameserver = "8.8.8.8";
-          }
-        '';
+        example = {
+          nameserver = "8.8.8.8";
+        };
         description = ''
           Additional configuration for shadowsocks that is not covered by the
           provided options. The provided attrset will be serialized to JSON and
diff --git a/nixos/modules/services/networking/shellhub-agent.nix b/nixos/modules/services/networking/shellhub-agent.nix
index 4ce4b8250bc3c..a45ef148544f9 100644
--- a/nixos/modules/services/networking/shellhub-agent.nix
+++ b/nixos/modules/services/networking/shellhub-agent.nix
@@ -23,7 +23,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.shellhub-agent;
-        defaultText = "pkgs.shellhub-agent";
+        defaultText = literalExpression "pkgs.shellhub-agent";
         description = ''
           Which ShellHub Agent package to use.
         '';
diff --git a/nixos/modules/services/networking/shorewall.nix b/nixos/modules/services/networking/shorewall.nix
index 16383be2530f7..ac732d4b12e48 100644
--- a/nixos/modules/services/networking/shorewall.nix
+++ b/nixos/modules/services/networking/shorewall.nix
@@ -22,7 +22,7 @@ in {
       package = lib.mkOption {
         type        = types.package;
         default     = pkgs.shorewall;
-        defaultText = "pkgs.shorewall";
+        defaultText = lib.literalExpression "pkgs.shorewall";
         description = "The shorewall package to use.";
       };
       configs = lib.mkOption {
diff --git a/nixos/modules/services/networking/shorewall6.nix b/nixos/modules/services/networking/shorewall6.nix
index e081aedc6c344..4235c74a3f80c 100644
--- a/nixos/modules/services/networking/shorewall6.nix
+++ b/nixos/modules/services/networking/shorewall6.nix
@@ -22,7 +22,7 @@ in {
       package = lib.mkOption {
         type        = types.package;
         default     = pkgs.shorewall;
-        defaultText = "pkgs.shorewall";
+        defaultText = lib.literalExpression "pkgs.shorewall";
         description = "The shorewall package to use.";
       };
       configs = lib.mkOption {
diff --git a/nixos/modules/services/networking/shout.nix b/nixos/modules/services/networking/shout.nix
index 405808491ea43..cca03a8f88a1b 100644
--- a/nixos/modules/services/networking/shout.nix
+++ b/nixos/modules/services/networking/shout.nix
@@ -83,11 +83,13 @@ in {
 
   config = mkIf cfg.enable {
     users.users.shout = {
-      uid = config.ids.uids.shout;
+      isSystemUser = true;
+      group = "shout";
       description = "Shout daemon user";
       home = shoutHome;
       createHome = true;
     };
+    users.groups.shout = {};
 
     systemd.services.shout = {
       description = "Shout web IRC client";
diff --git a/nixos/modules/services/networking/skydns.nix b/nixos/modules/services/networking/skydns.nix
index ea466de932750..c4e959b57bbed 100644
--- a/nixos/modules/services/networking/skydns.nix
+++ b/nixos/modules/services/networking/skydns.nix
@@ -56,7 +56,7 @@ in {
 
     package = mkOption {
       default = pkgs.skydns;
-      defaultText = "pkgs.skydns";
+      defaultText = literalExpression "pkgs.skydns";
       type = types.package;
       description = "Skydns package to use.";
     };
diff --git a/nixos/modules/services/networking/smartdns.nix b/nixos/modules/services/networking/smartdns.nix
index f84c727f0343f..7f9df42ce9c18 100644
--- a/nixos/modules/services/networking/smartdns.nix
+++ b/nixos/modules/services/networking/smartdns.nix
@@ -32,7 +32,7 @@ in {
       type =
       let atom = oneOf [ str int bool ];
       in attrsOf (coercedTo atom toList (listOf atom));
-      example = literalExample ''
+      example = literalExpression ''
         {
           bind = ":5353 -no-rule -group example";
           cache-size = 4096;
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index 4470c18fd5330..c075cbbceac9a 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -60,7 +60,7 @@ in
           to = root@localhost
           from = smokeping@localhost
         '';
-        example = literalExample ''
+        example = ''
           to = alertee@address.somewhere
           from = smokealert@company.xy
 
@@ -75,7 +75,7 @@ in
       cgiUrl = mkOption {
         type = types.str;
         default = "http://${cfg.hostName}:${toString cfg.port}/smokeping.cgi";
-        defaultText = "http://\${hostName}:\${toString port}/smokeping.cgi";
+        defaultText = literalExpression ''"http://''${hostName}:''${toString port}/smokeping.cgi"'';
         example = "https://somewhere.example.com/smokeping.cgi";
         description = "URL to the smokeping cgi.";
       };
@@ -100,7 +100,7 @@ in
               MIN  0.5 144   720
 
         '';
-        example = literalExample ''
+        example = ''
           # near constant pings.
           step     = 30
           pings    = 20
@@ -125,16 +125,21 @@ in
       hostName = mkOption {
         type = types.str;
         default = config.networking.fqdn;
-        defaultText = "\${config.networking.fqdn}";
+        defaultText = literalExpression "config.networking.fqdn";
         example = "somewhere.example.com";
         description = "DNS name for the urls generated in the cgi.";
       };
       imgUrl = mkOption {
         type = types.str;
-        default = "http://${cfg.hostName}:${toString cfg.port}/cache";
-        defaultText = "http://\${hostName}:\${toString port}/cache";
+        default = "cache";
+        defaultText = literalExpression ''"cache"'';
         example = "https://somewhere.example.com/cache";
-        description = "Base url for images generated in the cgi.";
+        description = ''
+          Base url for images generated in the cgi.
+
+          The default is a relative URL to ensure it works also when e.g. forwarding
+          the GUI port via SSH.
+        '';
       };
       linkStyle = mkOption {
         type = types.enum ["original" "absolute" "relative"];
@@ -157,20 +162,30 @@ in
       ownerEmail = mkOption {
         type = types.str;
         default = "no-reply@${cfg.hostName}";
-        defaultText = "no-reply@\${hostName}";
+        defaultText = literalExpression ''"no-reply@''${hostName}"'';
         example = "no-reply@yourdomain.com";
         description = "Email contact for owner";
       };
       package = mkOption {
         type = types.package;
         default = pkgs.smokeping;
-        defaultText = "pkgs.smokeping";
+        defaultText = literalExpression "pkgs.smokeping";
         description = "Specify a custom smokeping package";
       };
+      host = mkOption {
+        type = types.nullOr types.str;
+        default = "localhost";
+        example = "192.0.2.1"; # rfc5737 example IP for documentation
+        description = ''
+          Host/IP to bind to for the web server.
+
+          Setting it to <literal>null</literal> skips passing the -h option to thttpd,
+          which makes it bind to all interfaces.
+        '';
+      };
       port = mkOption {
         type = types.int;
         default = 8081;
-        example = 8081;
         description = "TCP port to use for the web server.";
       };
       presentationConfig = mkOption {
@@ -217,6 +232,7 @@ in
       presentationTemplate = mkOption {
         type = types.str;
         default = "${pkgs.smokeping}/etc/basepage.html.dist";
+        defaultText = literalExpression ''"''${pkgs.smokeping}/etc/basepage.html.dist"'';
         description = "Default page layout for the web UI.";
       };
       probeConfig = mkOption {
@@ -236,6 +252,7 @@ in
       smokeMailTemplate = mkOption {
         type = types.str;
         default = "${cfg.package}/etc/smokemail.dist";
+        defaultText = literalExpression ''"''${package}/etc/smokemail.dist"'';
         description = "Specify the smokemail template for alerts.";
       };
       targetConfig = mkOption {
@@ -259,7 +276,7 @@ in
       user = mkOption {
         type = types.str;
         default = "smokeping";
-        description = "User that runs smokeping and (optionally) thttpd";
+        description = "User that runs smokeping and (optionally) thttpd. A group of the same name will be created as well.";
       };
       webService = mkOption {
         type = types.bool;
@@ -278,23 +295,29 @@ in
       }
     ];
     security.wrappers = {
-      fping.source = "${pkgs.fping}/bin/fping";
-      fping6.source = "${pkgs.fping}/bin/fping6";
+      fping =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.fping}/bin/fping";
+        };
     };
     environment.systemPackages = [ pkgs.fping ];
     users.users.${cfg.user} = {
       isNormalUser = false;
       isSystemUser = true;
-      uid = config.ids.uids.smokeping;
+      group = cfg.user;
       description = "smokeping daemon user";
       home = smokepingHome;
       createHome = true;
     };
+    users.groups.${cfg.user} = {};
     systemd.services.smokeping = {
-      wantedBy = [ "multi-user.target"];
+      requiredBy = [ "multi-user.target"];
       serviceConfig = {
         User = cfg.user;
         Restart = "on-failure";
+        ExecStart = "${cfg.package}/bin/smokeping --config=${configPath} --nodaemon";
       };
       preStart = ''
         mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data
@@ -305,18 +328,29 @@ in
         ${cfg.package}/bin/smokeping --check --config=${configPath}
         ${cfg.package}/bin/smokeping --static --config=${configPath}
       '';
-      script = "${cfg.package}/bin/smokeping --config=${configPath} --nodaemon";
     };
     systemd.services.thttpd = mkIf cfg.webService {
-      wantedBy = [ "multi-user.target"];
+      requiredBy = [ "multi-user.target"];
       requires = [ "smokeping.service"];
-      partOf = [ "smokeping.service"];
       path = with pkgs; [ bash rrdtool smokeping thttpd ];
-      script = ''thttpd -u ${cfg.user} -c "**.fcgi" -d ${smokepingHome} -p ${builtins.toString cfg.port} -D -nos'';
-      serviceConfig.Restart = "always";
+      serviceConfig = {
+        Restart = "always";
+        ExecStart = lib.concatStringsSep " " (lib.concatLists [
+          [ "${pkgs.thttpd}/bin/thttpd" ]
+          [ "-u ${cfg.user}" ]
+          [ ''-c "**.fcgi"'' ]
+          [ "-d ${smokepingHome}" ]
+          (lib.optional (cfg.host != null) "-h ${cfg.host}")
+          [ "-p ${builtins.toString cfg.port}" ]
+          [ "-D -nos" ]
+        ]);
+      };
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ erictapen ];
+  meta.maintainers = with lib.maintainers; [
+    erictapen
+    nh2
+  ];
 }
 
diff --git a/nixos/modules/services/networking/sniproxy.nix b/nixos/modules/services/networking/sniproxy.nix
index 0345c12d3afeb..28c201f0565e9 100644
--- a/nixos/modules/services/networking/sniproxy.nix
+++ b/nixos/modules/services/networking/sniproxy.nix
@@ -34,7 +34,7 @@ in
         type = types.lines;
         default = "";
         description = "sniproxy.conf configuration excluding the daemon username and pid file.";
-        example = literalExample ''
+        example = ''
           error_log {
             filename /var/log/sniproxy/error.log
           }
@@ -47,7 +47,7 @@ in
           table {
             example.com 192.0.2.10
             example.net 192.0.2.20
-        }
+          }
         '';
       };
 
diff --git a/nixos/modules/services/networking/softether.nix b/nixos/modules/services/networking/softether.nix
index 2dc73d81b258c..5405f56871e92 100644
--- a/nixos/modules/services/networking/softether.nix
+++ b/nixos/modules/services/networking/softether.nix
@@ -21,7 +21,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.softether;
-        defaultText = "pkgs.softether";
+        defaultText = literalExpression "pkgs.softether";
         description = ''
           softether derivation to use.
         '';
diff --git a/nixos/modules/services/networking/spacecookie.nix b/nixos/modules/services/networking/spacecookie.nix
index e0bef9e9628d6..400f3e26cc9a1 100644
--- a/nixos/modules/services/networking/spacecookie.nix
+++ b/nixos/modules/services/networking/spacecookie.nix
@@ -30,8 +30,8 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.spacecookie;
-        defaultText = literalExample "pkgs.spacecookie";
-        example = literalExample "pkgs.haskellPackages.spacecookie";
+        defaultText = literalExpression "pkgs.spacecookie";
+        example = literalExpression "pkgs.haskellPackages.spacecookie";
         description = ''
           The spacecookie derivation to use. This can be used to
           override the used package or to use another version.
diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix
index e60d9abf42a66..3c229ecfc72eb 100644
--- a/nixos/modules/services/networking/spiped.nix
+++ b/nixos/modules/services/networking/spiped.nix
@@ -138,7 +138,7 @@ in
 
         default = {};
 
-        example = literalExample ''
+        example = literalExpression ''
           {
             pipe1 =
               { keyfile = "/var/lib/spiped/pipe1.key";
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 225aee5160503..004b4f99670f8 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -401,9 +401,12 @@ in
   config = mkIf cfg.enable {
 
     users.users.sshd =
-      { isSystemUser = true;
+      {
+        isSystemUser = true;
+        group = "sshd";
         description = "SSH privilege separation user";
       };
+    users.groups.sshd = {};
 
     services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
     services.openssh.sftpServerExecutable = mkDefault "${cfgc.package}/libexec/sftp-server";
@@ -436,7 +439,7 @@ in
                 mkdir -m 0755 -p /etc/ssh
 
                 ${flip concatMapStrings cfg.hostKeys (k: ''
-                  if ! [ -f "${k.path}" ]; then
+                  if ! [ -s "${k.path}" ]; then
                       ssh-keygen \
                         -t "${k.type}" \
                         ${if k ? bits then "-b ${toString k.bits}" else ""} \
diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix
index 6e619f22546ff..9287943fcde33 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/module.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -13,7 +13,7 @@ in  {
     package = mkOption {
       type = types.package;
       default = pkgs.strongswan;
-      defaultText = "pkgs.strongswan";
+      defaultText = literalExpression "pkgs.strongswan";
       description = ''
         The strongswan derivation to use.
       '';
diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index 8ae62931a8f90..cca61b9ce930e 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -569,6 +569,16 @@ in {
         these sections offer more flexibility.
       '';
 
+      ca_id = mkOptionalStrParam ''
+        Identity in CA certificate to accept for authentication. The specified
+        identity must be contained in one (intermediate) CA of the remote peer
+        trustchain, either as subject or as subjectAltName. This has the same
+        effect as specifying <literal>cacerts</literal> to force clients under
+        a CA to specific connections; it does not require the CA certificate
+        to be available locally, and can be received from the peer during the
+        IKE exchange.
+      '';
+
       cacerts = mkCommaSepListParam [] ''
         List of CA certificates to accept for
         authentication. The certificates may use a relative path from the
diff --git a/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix
index 401f7be40288f..e3a97207be7f5 100644
--- a/nixos/modules/services/networking/strongswan.nix
+++ b/nixos/modules/services/networking/strongswan.nix
@@ -4,7 +4,7 @@ let
 
   inherit (builtins) toFile;
   inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList
-                mkIf mkEnableOption mkOption types literalExample;
+                mkIf mkEnableOption mkOption types literalExpression;
 
   cfg = config.services.strongswan;
 
@@ -79,7 +79,7 @@ in
     connections = mkOption {
       type = types.attrsOf (types.attrsOf types.str);
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "%default" = {
             keyexchange = "ikev2";
diff --git a/nixos/modules/services/networking/stunnel.nix b/nixos/modules/services/networking/stunnel.nix
index fe1616f411f0a..70d0a7d3c12ea 100644
--- a/nixos/modules/services/networking/stunnel.nix
+++ b/nixos/modules/services/networking/stunnel.nix
@@ -69,6 +69,7 @@ let
       CAFile = mkOption {
         type = types.nullOr types.path;
         default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+        defaultText = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
         description = "Path to a file containing certificates to validate against.";
       };
 
diff --git a/nixos/modules/services/networking/supplicant.nix b/nixos/modules/services/networking/supplicant.nix
index 4f4b5cef37413..eb24130e519a7 100644
--- a/nixos/modules/services/networking/supplicant.nix
+++ b/nixos/modules/services/networking/supplicant.nix
@@ -73,7 +73,7 @@ in
             path = mkOption {
               type = types.nullOr types.path;
               default = null;
-              example = literalExample "/etc/wpa_supplicant.conf";
+              example = literalExpression "/etc/wpa_supplicant.conf";
               description = ''
                 External <literal>wpa_supplicant.conf</literal> configuration file.
                 The configuration options defined declaratively within <literal>networking.supplicant</literal> have
@@ -170,7 +170,7 @@ in
 
       default = { };
 
-      example = literalExample ''
+      example = literalExpression ''
         { "wlan0 wlan1" = {
             configFile.path = "/etc/wpa_supplicant.conf";
             userControlled.group = "network";
diff --git a/nixos/modules/services/networking/supybot.nix b/nixos/modules/services/networking/supybot.nix
index 332c3ced06f07..94b79c7e247ff 100644
--- a/nixos/modules/services/networking/supybot.nix
+++ b/nixos/modules/services/networking/supybot.nix
@@ -24,7 +24,7 @@ in
         default = if versionAtLeast config.system.stateVersion "20.09"
           then "/var/lib/supybot"
           else "/home/supybot";
-        defaultText = "/var/lib/supybot";
+        defaultText = literalExpression "/var/lib/supybot";
         description = "The root directory, logs and plugins are stored here";
       };
 
@@ -49,7 +49,7 @@ in
           Please note that you still need to add the plugins to the config
           file (or with <literal>!load</literal>) using their attribute name.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           let
             plugins = pkgs.fetchzip {
               url = "https://github.com/ProgVal/Supybot-plugins/archive/57c2450c.zip";
@@ -66,12 +66,13 @@ in
       extraPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
         default = p: [];
+        defaultText = literalExpression "p: []";
         description = ''
           Extra Python packages available to supybot plugins. The
           value must be a function which receives the attrset defined
           in <varname>python3Packages</varname> as the sole argument.
         '';
-        example = literalExample "p: [ p.lxml p.requests ]";
+        example = literalExpression "p: [ p.lxml p.requests ]";
       };
 
     };
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index ebe4d89a0e7f3..8c44687a38224 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -182,7 +182,7 @@ in {
           will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link>
           is enabled.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             "/home/user/sync" = {
               id = "syncme";
@@ -243,7 +243,7 @@ in {
                 There are 4 different types of versioning with different parameters.
                 See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 [
                   {
                     versioning = {
@@ -430,8 +430,8 @@ in {
         description = ''
           The path where the settings and keys will exist.
         '';
-        default = cfg.dataDir + (optionalString cond "/.config/syncthing");
-        defaultText = literalExample "dataDir${optionalString cond " + \"/.config/syncthing\""}";
+        default = cfg.dataDir + optionalString cond "/.config/syncthing";
+        defaultText = literalExpression "dataDir${optionalString cond " + \"/.config/syncthing\""}";
       };
 
       extraFlags = mkOption {
@@ -461,7 +461,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.syncthing;
-        defaultText = literalExample "pkgs.syncthing";
+        defaultText = literalExpression "pkgs.syncthing";
         description = ''
           The Syncthing package to use.
         '';
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 3f88ff53dff0b..3f41646bf01e9 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -24,7 +24,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.tailscale;
-      defaultText = "pkgs.tailscale";
+      defaultText = literalExpression "pkgs.tailscale";
       description = "The package to use for tailscale";
     };
   };
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index fadb32dcd7770..c0ed08282aaf3 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -43,7 +43,7 @@ in
       voiceIP = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "0.0.0.0";
+        example = "[::]";
         description = ''
           IP on which the server instance will listen for incoming voice connections. Defaults to any IP.
         '';
@@ -60,7 +60,7 @@ in
       fileTransferIP = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "0.0.0.0";
+        example = "[::]";
         description = ''
           IP on which the server instance will listen for incoming file transfer connections. Defaults to any IP.
         '';
@@ -91,6 +91,18 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the TeamSpeak3 server.";
+      };
+
+      openFirewallServerQuery = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the TeamSpeak3 serverquery (administration) system. Requires openFirewall.";
+      };
+
     };
 
   };
@@ -115,6 +127,12 @@ in
       "d '${cfg.logPath}' - ${user} ${group} - -"
     ];
 
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.fileTransferPort ] ++ optionals (cfg.openFirewallServerQuery) [ cfg.queryPort (cfg.queryPort + 11) ];
+      # subsequent vServers will use the incremented voice port, let's just open the next 10
+      allowedUDPPortRanges = [ { from = cfg.defaultVoicePort; to = cfg.defaultVoicePort + 10; } ];
+    };
+
     systemd.services.teamspeak3-server = {
       description = "Teamspeak3 voice communication server daemon";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/tedicross.nix b/nixos/modules/services/networking/tedicross.nix
index 0716975f594a2..c7830289dca05 100644
--- a/nixos/modules/services/networking/tedicross.nix
+++ b/nixos/modules/services/networking/tedicross.nix
@@ -18,7 +18,7 @@ in {
       config = mkOption {
         type = types.attrs;
         # from https://github.com/TediCross/TediCross/blob/master/example.settings.yaml
-        example = literalExample ''
+        example = literalExpression ''
           {
             telegram = {
               useFirstNameInsteadOfUsername = false;
diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
index a1b06703484b0..b944916391631 100644
--- a/nixos/modules/services/networking/thelounge.nix
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -32,7 +32,7 @@ in {
     extraConfig = mkOption {
       default = {};
       type = types.attrs;
-      example = literalExample ''{
+      example = literalExpression ''{
         reverseProxy = true;
         defaults = {
           name = "Your Network";
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 22caf9f4ec56c..9db433fa0735c 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -226,7 +226,7 @@ in
 
             hostSettings = mkOption {
               default = { };
-              example = literalExample ''
+              example = literalExpression ''
                 {
                   host1 = {
                     addresses = [
@@ -282,27 +282,27 @@ in
             package = mkOption {
               type = types.package;
               default = pkgs.tinc_pre;
-              defaultText = "pkgs.tinc_pre";
+              defaultText = literalExpression "pkgs.tinc_pre";
               description = ''
                 The package to use for the tinc daemon's binary.
               '';
             };
 
             chroot = mkOption {
-              default = true;
+              default = false;
               type = types.bool;
               description = ''
                 Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
                 The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
 
-                Note that tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
+                Note that this currently breaks dns resolution and tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
               '';
             };
 
             settings = mkOption {
               default = { };
               type = types.submodule { freeformType = tincConfType; };
-              example = literalExample ''
+              example = literalExpression ''
                 {
                   Interface = "custom.interface";
                   DirectOnly = true;
diff --git a/nixos/modules/services/networking/tinydns.nix b/nixos/modules/services/networking/tinydns.nix
index 79507b2ebcddb..2c44ad49296d7 100644
--- a/nixos/modules/services/networking/tinydns.nix
+++ b/nixos/modules/services/networking/tinydns.nix
@@ -32,7 +32,11 @@ with lib;
   config = mkIf config.services.tinydns.enable {
     environment.systemPackages = [ pkgs.djbdns ];
 
-    users.users.tinydns.isSystemUser = true;
+    users.users.tinydns = {
+      isSystemUser = true;
+      group = "tinydns";
+    };
+    users.groups.tinydns = {};
 
     systemd.services.tinydns = {
       description = "djbdns tinydns server";
diff --git a/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix
index f88e34827d00f..7c13724e084a2 100644
--- a/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -3,15 +3,15 @@
 with lib;
 
 let
-  home = "/var/lib/tox-bootstrapd";
-  PIDFile = "${home}/pid";
+  WorkingDirectory = "/var/lib/tox-bootstrapd";
+  PIDFile = "${WorkingDirectory}/pid";
 
   pkg = pkgs.libtoxcore;
   cfg = config.services.toxBootstrapd;
   cfgFile = builtins.toFile "tox-bootstrapd.conf"
     ''
       port = ${toString cfg.port}
-      keys_file_path = "${home}/keys"
+      keys_file_path = "${WorkingDirectory}/keys"
       pid_file_path = "${PIDFile}"
       ${cfg.extraConfig}
     '';
@@ -36,7 +36,7 @@ in
 
           keysFile = mkOption {
             type = types.str;
-            default = "${home}/keys";
+            default = "${WorkingDirectory}/keys";
             description = "Node key file.";
           };
 
@@ -56,13 +56,6 @@ in
 
   config = mkIf config.services.toxBootstrapd.enable {
 
-    users.users.tox-bootstrapd =
-      { uid = config.ids.uids.tox-bootstrapd;
-        description = "Tox bootstrap daemon user";
-        inherit home;
-        createHome = true;
-      };
-
     systemd.services.tox-bootstrapd = {
       description = "Tox DHT bootstrap daemon";
       after = [ "network.target" ];
@@ -70,8 +63,10 @@ in
       serviceConfig =
         { ExecStart = "${pkg}/bin/tox-bootstrapd --config=${cfgFile}";
           Type = "forking";
-          inherit PIDFile;
-          User = "tox-bootstrapd";
+          inherit PIDFile WorkingDirectory;
+          AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
+          DynamicUser = true;
+          StateDirectory = "tox-bootstrapd";
         };
     };
 
diff --git a/nixos/modules/services/networking/toxvpn.nix b/nixos/modules/services/networking/toxvpn.nix
index 9e97faeebc1e8..18cf7672d5f46 100644
--- a/nixos/modules/services/networking/toxvpn.nix
+++ b/nixos/modules/services/networking/toxvpn.nix
@@ -22,7 +22,7 @@ with lib;
       auto_add_peers = mkOption {
         type        = types.listOf types.str;
         default     = [];
-        example     = ''[ "toxid1" "toxid2" ]'';
+        example     = [ "toxid1" "toxid2" ];
         description = "peers to automatically connect to on startup";
       };
     };
@@ -59,10 +59,12 @@ with lib;
 
     users.users = {
       toxvpn = {
-        uid        = config.ids.uids.toxvpn;
+        isSystemUser = true;
+        group = "toxvpn";
         home       = "/var/lib/toxvpn";
         createHome = true;
       };
     };
+    users.groups.toxvpn = {};
   };
 }
diff --git a/nixos/modules/services/networking/trickster.nix b/nixos/modules/services/networking/trickster.nix
index 49c945adb80f3..e48bba8fa587f 100644
--- a/nixos/modules/services/networking/trickster.nix
+++ b/nixos/modules/services/networking/trickster.nix
@@ -20,7 +20,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.trickster;
-        defaultText = "pkgs.trickster";
+        defaultText = literalExpression "pkgs.trickster";
         description = ''
           Package that should be used for trickster.
         '';
diff --git a/nixos/modules/services/networking/tvheadend.nix b/nixos/modules/services/networking/tvheadend.nix
index ccf879996631d..19a10a03bd9b6 100644
--- a/nixos/modules/services/networking/tvheadend.nix
+++ b/nixos/modules/services/networking/tvheadend.nix
@@ -29,8 +29,10 @@ in
       description = "Tvheadend Service user";
       home        = "/var/lib/tvheadend";
       createHome  = true;
-      uid         = config.ids.uids.tvheadend;
+      isSystemUser = true;
+      group = "tvheadend";
     };
+    users.groups.tvheadend = {};
 
     systemd.services.tvheadend = {
       description = "Tvheadend TV streaming server";
diff --git a/nixos/modules/services/networking/ucarp.nix b/nixos/modules/services/networking/ucarp.nix
index 9b19a19687bca..189e4f99cefec 100644
--- a/nixos/modules/services/networking/ucarp.nix
+++ b/nixos/modules/services/networking/ucarp.nix
@@ -91,10 +91,10 @@ in {
         Command to run after become master, the interface name, virtual address
         and optional extra parameters are passed as arguments.
       '';
-      example = ''
+      example = literalExpression ''
         pkgs.writeScript "upscript" '''
           #!/bin/sh
-          $\{pkgs.iproute2\}/bin/ip addr add "$2"/24 dev "$1"
+          ''${pkgs.iproute2}/bin/ip addr add "$2"/24 dev "$1"
         ''';
       '';
     };
@@ -105,10 +105,10 @@ in {
         Command to run after become backup, the interface name, virtual address
         and optional extra parameters are passed as arguments.
       '';
-      example = ''
+      example = literalExpression ''
         pkgs.writeScript "downscript" '''
           #!/bin/sh
-          $\{pkgs.iproute2\}/bin/ip addr del "$2"/24 dev "$1"
+          ''${pkgs.iproute2}/bin/ip addr del "$2"/24 dev "$1"
         ''';
       '';
     };
@@ -152,7 +152,7 @@ in {
         upstream updates for a long time and can be considered as unmaintained.
       '';
       default = pkgs.ucarp;
-      defaultText = "pkgs.ucarp";
+      defaultText = literalExpression "pkgs.ucarp";
     };
   };
 
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 6d7178047ea89..f6e9634909241 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -45,7 +45,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.unbound-with-systemd;
-        defaultText = "pkgs.unbound-with-systemd";
+        defaultText = literalExpression "pkgs.unbound-with-systemd";
         description = "The unbound package to use";
       };
 
@@ -128,7 +128,7 @@ in {
             };
           };
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             server = {
               interface = [ "127.0.0.1" ];
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 2e320378cc9a6..53ad4df477fcc 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -9,25 +9,6 @@ let
         ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
         -jar ${stateDir}/lib/ace.jar
   '';
-  mountPoints = [
-    {
-      what = "${cfg.unifiPackage}/dl";
-      where = "${stateDir}/dl";
-    }
-    {
-      what = "${cfg.unifiPackage}/lib";
-      where = "${stateDir}/lib";
-    }
-    {
-      what = "${cfg.mongodbPackage}/bin";
-      where = "${stateDir}/bin";
-    }
-    {
-      what = "${cfg.dataDir}";
-      where = "${stateDir}/data";
-    }
-  ];
-  systemdMountPoints = map (m: "${utils.escapeSystemdPath m.where}.mount") mountPoints;
 in
 {
 
@@ -44,7 +25,7 @@ in
     services.unifi.jrePackage = mkOption {
       type = types.package;
       default = pkgs.jre8;
-      defaultText = "pkgs.jre8";
+      defaultText = literalExpression "pkgs.jre8";
       description = ''
         The JRE package to use. Check the release notes to ensure it is supported.
       '';
@@ -53,7 +34,7 @@ in
     services.unifi.unifiPackage = mkOption {
       type = types.package;
       default = pkgs.unifiLTS;
-      defaultText = "pkgs.unifiLTS";
+      defaultText = literalExpression "pkgs.unifiLTS";
       description = ''
         The unifi package to use.
       '';
@@ -62,22 +43,12 @@ in
     services.unifi.mongodbPackage = mkOption {
       type = types.package;
       default = pkgs.mongodb;
-      defaultText = "pkgs.mongodb";
+      defaultText = literalExpression "pkgs.mongodb";
       description = ''
         The mongodb package to use.
       '';
     };
 
-    services.unifi.dataDir = mkOption {
-      type = types.str;
-      default = "${stateDir}/data";
-      description = ''
-        Where to store the database and other data.
-
-        This directory will be bind-mounted to ${stateDir}/data as part of the service startup.
-      '';
-    };
-
     services.unifi.openPorts = mkOption {
       type = types.bool;
       default = true;
@@ -115,10 +86,12 @@ in
   config = mkIf cfg.enable {
 
     users.users.unifi = {
-      uid = config.ids.uids.unifi;
+      isSystemUser = true;
+      group = "unifi";
       description = "UniFi controller daemon user";
       home = "${stateDir}";
     };
+    users.groups.unifi = {};
 
     networking.firewall = mkIf cfg.openPorts {
       # https://help.ubnt.com/hc/en-us/articles/218506997
@@ -134,32 +107,11 @@ in
       ];
     };
 
-    # We must create the binary directories as bind mounts instead of symlinks
-    # This is because the controller resolves all symlinks to absolute paths
-    # to be used as the working directory.
-    systemd.mounts = map ({ what, where }: {
-        bindsTo = [ "unifi.service" ];
-        partOf = [ "unifi.service" ];
-        unitConfig.RequiresMountsFor = stateDir;
-        options = "bind";
-        what = what;
-        where = where;
-      }) mountPoints;
-
-    systemd.tmpfiles.rules = [
-      "d '${stateDir}' 0700 unifi - - -"
-      "d '${stateDir}/data' 0700 unifi - - -"
-      "d '${stateDir}/webapps' 0700 unifi - - -"
-      "L+ '${stateDir}/webapps/ROOT' - - - - ${cfg.unifiPackage}/webapps/ROOT"
-    ];
-
     systemd.services.unifi = {
       description = "UniFi controller daemon";
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ] ++ systemdMountPoints;
-      partOf = systemdMountPoints;
-      bindsTo = systemdMountPoints;
-      unitConfig.RequiresMountsFor = stateDir;
+      after = [ "network.target" ];
+
       # 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
@@ -170,9 +122,15 @@ in
         ExecStart = "${(removeSuffix "\n" cmd)} start";
         ExecStop = "${(removeSuffix "\n" cmd)} stop";
         Restart = "on-failure";
+        TimeoutSec = "5min";
         User = "unifi";
         UMask = "0077";
         WorkingDirectory = "${stateDir}";
+        # the stop command exits while the main process is still running, and unifi
+        # wants to manage its own child processes. this means we have to set KillSignal
+        # to something the main process ignores, otherwise every stop will have unifi.service
+        # fail with SIGTERM status.
+        KillSignal = "SIGCONT";
 
         # Hardening
         AmbientCapabilities = "";
@@ -201,8 +159,27 @@ in
         SystemCallErrorNumber = "EPERM";
         SystemCallFilter = [ "@system-service" ];
 
-        # Required for ProtectSystem=strict
-        BindPaths = [ stateDir ];
+        StateDirectory = "unifi";
+        RuntimeDirectory = "unifi";
+        LogsDirectory = "unifi";
+        CacheDirectory= "unifi";
+
+        TemporaryFileSystem = [
+          # required as we want to create bind mounts below
+          "${stateDir}/webapps:rw"
+        ];
+
+        # We must create the binary directories as bind mounts instead of symlinks
+        # This is because the controller resolves all symlinks to absolute paths
+        # to be used as the working directory.
+        BindPaths =  [
+          "/var/log/unifi:${stateDir}/logs"
+          "/run/unifi:${stateDir}/run"
+          "${cfg.unifiPackage}/dl:${stateDir}/dl"
+          "${cfg.unifiPackage}/lib:${stateDir}/lib"
+          "${cfg.mongodbPackage}/bin:${stateDir}/bin"
+          "${cfg.unifiPackage}/webapps/ROOT:${stateDir}/webapps/ROOT"
+        ];
 
         # Needs network access
         PrivateNetwork = false;
@@ -212,6 +189,9 @@ in
     };
 
   };
+  imports = [
+    (mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data" )
+  ];
 
-  meta.maintainers = with lib.maintainers; [ erictapen ];
+  meta.maintainers = with lib.maintainers; [ erictapen pennae ];
 }
diff --git a/nixos/modules/services/networking/v2ray.nix b/nixos/modules/services/networking/v2ray.nix
index 0b8b5b56e25ba..95e8761ba5cc5 100644
--- a/nixos/modules/services/networking/v2ray.nix
+++ b/nixos/modules/services/networking/v2ray.nix
@@ -16,6 +16,15 @@ with lib;
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.v2ray;
+        defaultText = literalExpression "pkgs.v2ray";
+        description = ''
+          Which v2ray package to use.
+        '';
+      };
+
       configFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -62,7 +71,7 @@ with lib;
         name = "v2ray.json";
         text = builtins.toJSON cfg.config;
         checkPhase = ''
-          ${pkgs.v2ray}/bin/v2ray -test -config $out
+          ${cfg.package}/bin/v2ray -test -config $out
         '';
       };
 
@@ -78,10 +87,9 @@ with lib;
       description = "v2ray Daemon";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.v2ray ];
-      script = ''
-        exec v2ray -config ${configFile}
-      '';
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/v2ray -config ${configFile}";
+      };
     };
   };
 }
diff --git a/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix
index c57994533c175..710c2d9ca17b6 100644
--- a/nixos/modules/services/networking/vsftpd.nix
+++ b/nixos/modules/services/networking/vsftpd.nix
@@ -159,7 +159,7 @@ in
       userlistFile = mkOption {
         type = types.path;
         default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
-        defaultText = "pkgs.writeText \"userlist\" (concatMapStrings (x: \"\${x}\n\") cfg.userlist)";
+        defaultText = literalExpression ''pkgs.writeText "userlist" (concatMapStrings (x: "''${x}\n") cfg.userlist)'';
         description = ''
           Newline separated list of names to be allowed/denied if <option>userlistEnable</option>
           is <literal>true</literal>. Meaning see <option>userlistDeny</option>.
@@ -282,7 +282,8 @@ in
 
     users.users = {
       "vsftpd" = {
-        uid = config.ids.uids.vsftpd;
+        group = "vsftpd";
+        isSystemUser = true;
         description = "VSFTPD user";
         home = if cfg.localRoot != null
                then cfg.localRoot # <= Necessary for virtual users.
@@ -297,6 +298,7 @@ in
         };
     };
 
+    users.groups.vsftpd = {};
     users.groups.ftp.gid = config.ids.gids.ftp;
 
     # If you really have to access root via FTP use mkOverride or userlistDeny
diff --git a/nixos/modules/services/networking/wakeonlan.nix b/nixos/modules/services/networking/wakeonlan.nix
deleted file mode 100644
index c6291366b0f14..0000000000000
--- a/nixos/modules/services/networking/wakeonlan.nix
+++ /dev/null
@@ -1,70 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  interfaces = config.services.wakeonlan.interfaces;
-
-  ethtool = "${pkgs.ethtool}/sbin/ethtool";
-
-  passwordParameter = password : if (password == "") then "" else
-    "sopass ${password}";
-
-  methodParameter = {method, password} :
-    if method == "magicpacket" then "wol g"
-    else if method == "password" then "wol s so ${passwordParameter password}"
-    else throw "Wake-On-Lan method not supported";
-
-  line = { interface, method ? "magicpacket", password ? "" }: ''
-    ${ethtool} -s ${interface} ${methodParameter {inherit method password;}}
-  '';
-
-  concatStrings = foldr (x: y: x + y) "";
-  lines = concatStrings (map (l: line l) interfaces);
-
-in
-{
-
-  ###### interface
-
-  options = {
-
-    services.wakeonlan.interfaces = mkOption {
-      default = [ ];
-      type = types.listOf (types.submodule { options = {
-        interface = mkOption {
-          type = types.str;
-          description = "Interface to enable for Wake-On-Lan.";
-        };
-        method = mkOption {
-          type = types.enum [ "magicpacket" "password"];
-          description = "Wake-On-Lan method for this interface.";
-        };
-        password = mkOption {
-          type = types.strMatching "[a-fA-F0-9]{2}:([a-fA-F0-9]{2}:){4}[a-fA-F0-9]{2}";
-          description = "The password has the shape of six bytes in hexadecimal separated by a colon each.";
-        };
-      };});
-      example = [
-        {
-          interface = "eth0";
-          method = "password";
-          password = "00:11:22:33:44:55";
-        }
-      ];
-      description = ''
-        Interfaces where to enable Wake-On-LAN, and how. Two methods available:
-        "magicpacket" and "password". The password has the shape of six bytes
-        in hexadecimal separated by a colon each. For more information,
-        check the ethtool manual.
-      '';
-    };
-
-  };
-
-
-  ###### implementation
-
-  config.powerManagement.powerUpCommands = lines;
-
-}
diff --git a/nixos/modules/services/networking/websockify.nix b/nixos/modules/services/networking/websockify.nix
index 27cb47be12f7e..f7e014e03efb5 100644
--- a/nixos/modules/services/networking/websockify.nix
+++ b/nixos/modules/services/networking/websockify.nix
@@ -21,7 +21,7 @@ let cfg = config.services.networking.websockify; in {
       sslKey = mkOption {
         description = "Path to the SSL key.";
         default = cfg.sslCert;
-        defaultText = "config.services.networking.websockify.sslCert";
+        defaultText = literalExpression "config.services.networking.websockify.sslCert";
         type = types.path;
       };
 
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 3b76de58548fb..414775fc35774 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -56,9 +56,7 @@ let
       };
 
       preUp = mkOption {
-        example = literalExample ''
-          ${pkgs.iproute2}/bin/ip netns add foo
-        '';
+        example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
         description = ''
@@ -67,9 +65,7 @@ let
       };
 
       preDown = mkOption {
-        example = literalExample ''
-          ${pkgs.iproute2}/bin/ip netns del foo
-        '';
+        example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
         description = ''
@@ -78,9 +74,7 @@ let
       };
 
       postUp = mkOption {
-        example = literalExample ''
-          ${pkgs.iproute2}/bin/ip netns add foo
-        '';
+        example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
         description = ''
@@ -89,9 +83,7 @@ let
       };
 
       postDown = mkOption {
-        example = literalExample ''
-          ${pkgs.iproute2}/bin/ip netns del foo
-        '';
+        example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
         description = ''
diff --git a/nixos/modules/services/networking/wicd.nix b/nixos/modules/services/networking/wicd.nix
deleted file mode 100644
index aa10a50f876a7..0000000000000
--- a/nixos/modules/services/networking/wicd.nix
+++ /dev/null
@@ -1,40 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-
-  ###### interface
-
-  options = {
-
-    networking.wicd.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to start <command>wicd</command>. Wired and
-        wireless network configurations can then be managed by
-        wicd-client.
-      '';
-    };
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.networking.wicd.enable {
-
-    environment.systemPackages = [pkgs.wicd];
-
-    systemd.services.wicd = {
-      after = [ "network-pre.target" ];
-      before = [ "network.target" ];
-      wants = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      script = "${pkgs.wicd}/sbin/wicd -f";
-    };
-
-    services.dbus.enable = true;
-    services.dbus.packages = [pkgs.wicd];
-  };
-}
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 2b51770a5aa13..55b84935b6cb5 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -62,9 +62,7 @@ let
       };
 
       preSetup = mkOption {
-        example = literalExample ''
-          ${pkgs.iproute2}/bin/ip netns add foo
-        '';
+        example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
         description = ''
@@ -73,8 +71,8 @@ let
       };
 
       postSetup = mkOption {
-        example = literalExample ''
-          printf "nameserver 10.200.100.1" | ${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0
+        example = literalExpression ''
+          '''printf "nameserver 10.200.100.1" | ''${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0'''
         '';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
@@ -82,7 +80,7 @@ let
       };
 
       postShutdown = mkOption {
-        example = literalExample "${pkgs.openresolv}/bin/resolvconf -d wg0";
+        example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
         description = "Commands called after shutting down the interface.";
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 155c6fdd0ab0c..4aa350d21a2ba 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -20,10 +20,16 @@ let
     ++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"''
     ++ optional (cfg.extraConfig != "") cfg.extraConfig);
 
+  configIsGenerated = with cfg;
+    networks != {} || extraConfig != "" || userControlled.enable;
+
+  # the original configuration file
   configFile =
-    if cfg.networks != {} || cfg.extraConfig != "" || cfg.userControlled.enable
+    if configIsGenerated
       then pkgs.writeText "wpa_supplicant.conf" generatedConfig
       else "/etc/wpa_supplicant.conf";
+  # the config file with environment variables replaced
+  finalConfig = ''"$RUNTIME_DIRECTORY"/wpa_supplicant.conf'';
 
   # Creates a network block for wpa_supplicant.conf
   mkNetwork = ssid: opts:
@@ -56,8 +62,8 @@ let
     let
       deviceUnit = optional (iface != null) "sys-subsystem-net-devices-${utils.escapeSystemdPath iface}.device";
       configStr = if cfg.allowAuxiliaryImperativeNetworks
-        then "-c /etc/wpa_supplicant.conf -I ${configFile}"
-        else "-c ${configFile}";
+        then "-c /etc/wpa_supplicant.conf -I ${finalConfig}"
+        else "-c ${finalConfig}";
     in {
       description = "WPA Supplicant instance" + optionalString (iface != null) " for interface ${iface}";
 
@@ -69,12 +75,25 @@ let
       stopIfChanged = false;
 
       path = [ package ];
+      serviceConfig.RuntimeDirectory = "wpa_supplicant";
+      serviceConfig.RuntimeDirectoryMode = "700";
+      serviceConfig.EnvironmentFile = mkIf (cfg.environmentFile != null)
+        (builtins.toString cfg.environmentFile);
 
       script =
       ''
-        if [ -f /etc/wpa_supplicant.conf -a "/etc/wpa_supplicant.conf" != "${configFile}" ]; then
-          echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
-        fi
+        ${optionalString configIsGenerated ''
+          if [ -f /etc/wpa_supplicant.conf ]; then
+            echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
+          fi
+        ''}
+
+        # substitute environment variables
+        ${pkgs.gawk}/bin/awk '{
+          for(varname in ENVIRON)
+            gsub("@"varname"@", ENVIRON[varname])
+          print
+        }' "${configFile}" > "${finalConfig}"
 
         iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
 
@@ -155,6 +174,44 @@ in {
         '';
       };
 
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/secrets/wireless.env";
+        description = ''
+          File consisting of lines of the form <literal>varname=value</literal>
+          to define variables for the wireless configuration.
+
+          See section "EnvironmentFile=" in <citerefentry>
+          <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
+          </citerefentry> for a syntax reference.
+
+          Secrets (PSKs, passwords, etc.) can be provided without adding them to
+          the world-readable Nix store by defining them in the environment file and
+          referring to them in option <option>networking.wireless.networks</option>
+          with the syntax <literal>@varname@</literal>. Example:
+
+          <programlisting>
+          # content of /run/secrets/wireless.env
+          PSK_HOME=mypassword
+          PASS_WORK=myworkpassword
+          </programlisting>
+
+          <programlisting>
+          # wireless-related configuration
+          networking.wireless.environmentFile = "/run/secrets/wireless.env";
+          networking.wireless.networks = {
+            home.psk = "@PSK_HOME@";
+            work.auth = '''
+              eap=PEAP
+              identity="my-user@example.com"
+              password="@PASS_WORK@"
+            ''';
+          };
+          </programlisting>
+        '';
+      };
+
       networks = mkOption {
         type = types.attrsOf (types.submodule {
           options = {
@@ -165,10 +222,14 @@ in {
                 The network's pre-shared key in plaintext defaulting
                 to being a network without any authentication.
 
-                Be aware that these will be written to the nix store
-                in plaintext!
+                <warning><para>
+                  Be aware that this will be written to the nix store
+                  in plaintext! Use an environment variable instead.
+                </para></warning>
 
-                Mutually exclusive with <varname>pskRaw</varname>.
+                <note><para>
+                  Mutually exclusive with <varname>pskRaw</varname>.
+                </para></note>
               '';
             };
 
@@ -179,7 +240,14 @@ in {
                 The network's pre-shared key in hex defaulting
                 to being a network without any authentication.
 
-                Mutually exclusive with <varname>psk</varname>.
+                <warning><para>
+                  Be aware that this will be written to the nix store
+                  in plaintext! Use an environment variable instead.
+                </para></warning>
+
+                <note><para>
+                  Mutually exclusive with <varname>psk</varname>.
+                </para></note>
               '';
             };
 
@@ -231,7 +299,7 @@ in {
               example = ''
                 eap=PEAP
                 identity="user@example.com"
-                password="secret"
+                password="@EXAMPLE_PASSWORD@"
               '';
               description = ''
                 Use this option to configure advanced authentication methods like EAP.
@@ -242,7 +310,15 @@ in {
                 </citerefentry>
                 for example configurations.
 
-                Mutually exclusive with <varname>psk</varname> and <varname>pskRaw</varname>.
+                <warning><para>
+                  Be aware that this will be written to the nix store
+                  in plaintext! Use an environment variable for secrets.
+                </para></warning>
+
+                <note><para>
+                  Mutually exclusive with <varname>psk</varname> and
+                  <varname>pskRaw</varname>.
+                </para></note>
               '';
             };
 
@@ -252,7 +328,7 @@ in {
               description = ''
                 Set this to <literal>true</literal> if the SSID of the network is hidden.
               '';
-              example = literalExample ''
+              example = literalExpression ''
                 { echelon = {
                     hidden = true;
                     psk = "abcdefgh";
@@ -301,13 +377,19 @@ in {
           /etc/wpa_supplicant.conf as the configuration file.
         '';
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           { echelon = {                   # SSID with no spaces or special characters
-              psk = "abcdefgh";
+              psk = "abcdefgh";           # (password will be written to /nix/store!)
             };
+
+            echelon = {                   # safe version of the above: read PSK from the
+              psk = "@PSK_ECHELON@";      # variable PSK_ECHELON, defined in environmentFile,
+            };                            # this won't leak into /nix/store
+
             "echelon's AP" = {            # SSID with spaces and/or special characters
-               psk = "ijklmnop";
+               psk = "ijklmnop";          # (password will be written to /nix/store!)
             };
+
             "free.wifi" = {};             # Public wireless network
           }
         '';
diff --git a/nixos/modules/services/networking/x2goserver.nix b/nixos/modules/services/networking/x2goserver.nix
index 48020fc1ceca4..d4adf6c5650e9 100644
--- a/nixos/modules/services/networking/x2goserver.nix
+++ b/nixos/modules/services/networking/x2goserver.nix
@@ -42,7 +42,6 @@ in {
     nxagentDefaultOptions = mkOption {
       type = types.listOf types.str;
       default = [ "-extension GLX" "-nolisten tcp" ];
-      example = [ "-extension GLX" "-nolisten tcp" ];
       description = ''
         List of default nx agent options.
       '';
@@ -55,12 +54,14 @@ in {
         x2goserver.conf ini configuration as nix attributes. See
         `x2goserver.conf(5)` for details
       '';
-      example = literalExample ''
-        superenicer = {
-          "enable" = "yes";
-          "idle-nice-level" = 19;
-        };
-        telekinesis = { "enable" = "no"; };
+      example = literalExpression ''
+        {
+          superenicer = {
+            "enable" = "yes";
+            "idle-nice-level" = 19;
+          };
+          telekinesis = { "enable" = "no"; };
+        }
       '';
     };
   };
@@ -88,12 +89,14 @@ in {
       source = "${pkgs.x2goserver}/lib/x2go/libx2go-server-db-sqlite3-wrapper.pl";
       owner = "x2go";
       group = "x2go";
+      setuid = false;
       setgid = true;
     };
     security.wrappers.x2goprintWrapper = {
       source = "${pkgs.x2goserver}/bin/x2goprint";
       owner = "x2go";
       group = "x2go";
+      setuid = false;
       setgid = true;
     };
 
diff --git a/nixos/modules/services/networking/xandikos.nix b/nixos/modules/services/networking/xandikos.nix
index 3c40bb956f57e..4bd45a76e673f 100644
--- a/nixos/modules/services/networking/xandikos.nix
+++ b/nixos/modules/services/networking/xandikos.nix
@@ -14,7 +14,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.xandikos;
-        defaultText = "pkgs.xandikos";
+        defaultText = literalExpression "pkgs.xandikos";
         description = "The Xandikos package to use.";
       };
 
@@ -45,7 +45,7 @@ in
       extraOptions = mkOption {
         default = [];
         type = types.listOf types.str;
-        example = literalExample ''
+        example = literalExpression ''
           [ "--autocreate"
             "--defaults"
             "--current-user-principal user"
diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix
index 9be7c3233e26b..c4f828f3c5a6b 100644
--- a/nixos/modules/services/networking/xrdp.nix
+++ b/nixos/modules/services/networking/xrdp.nix
@@ -47,7 +47,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.xrdp;
-        defaultText = "pkgs.xrdp";
+        defaultText = literalExpression "pkgs.xrdp";
         description = ''
           The package to use for the xrdp daemon's binary.
         '';
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 47a7152f6fe6e..99c18ae6919ea 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -99,7 +99,7 @@ in {
       package = mkOption {
         type = package;
         default = pkgs.yggdrasil;
-        defaultText = "pkgs.yggdrasil";
+        defaultText = literalExpression "pkgs.yggdrasil";
         description = "Yggdrasil package to use.";
       };
 
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index a34b2d8715410..3370390a4c626 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) generators literalExample mkEnableOption mkIf mkOption recursiveUpdate types;
+  inherit (lib) generators literalExpression mkEnableOption mkIf mkOption recursiveUpdate types;
   cfg = config.services.zeronet;
   dataDir = "/var/lib/zeronet";
   configFile = pkgs.writeText "zeronet.conf" (generators.toINI {} (recursiveUpdate defaultSettings cfg.settings));
@@ -22,7 +22,7 @@ in with lib; {
     settings = mkOption {
       type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
       default = {};
-      example = literalExample "global.tor = enable;";
+      example = literalExpression "{ global.tor = enable; }";
 
       description = ''
         <filename>zeronet.conf</filename> configuration. Refer to
@@ -34,7 +34,6 @@ in with lib; {
     port = mkOption {
       type = types.port;
       default = 43110;
-      example = 43110;
       description = "Optional zeronet web UI port.";
     };
 
@@ -43,7 +42,6 @@ in with lib; {
       # read-only config file and crashes
       type = types.port;
       default = 12261;
-      example = 12261;
       description = "Zeronet fileserver port.";
     };
 
diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix
index cf39ed065a769..3bc7d3ac0db56 100644
--- a/nixos/modules/services/networking/zerotierone.nix
+++ b/nixos/modules/services/networking/zerotierone.nix
@@ -19,7 +19,6 @@ in
 
   options.services.zerotierone.port = mkOption {
     default = 9993;
-    example = 9993;
     type = types.int;
     description = ''
       Network port used by ZeroTier.
@@ -28,7 +27,7 @@ in
 
   options.services.zerotierone.package = mkOption {
     default = pkgs.zerotierone;
-    defaultText = "pkgs.zerotierone";
+    defaultText = literalExpression "pkgs.zerotierone";
     type = types.package;
     description = ''
       ZeroTier One package to use.
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
index b872b99976ce7..a98f92d2d710c 100644
--- a/nixos/modules/services/networking/znc/default.nix
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -125,7 +125,7 @@ in
       config = mkOption {
         type = semanticTypes.zncConf;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             LoadModule = [ "webadmin" "adminlog" ];
             User.paul = {
@@ -180,7 +180,7 @@ in
 
       configFile = mkOption {
         type = types.path;
-        example = "~/.znc/configs/znc.conf";
+        example = literalExpression "~/.znc/configs/znc.conf";
         description = ''
           Configuration file for ZNC. It is recommended to use the
           <option>config</option> option instead.
@@ -195,7 +195,7 @@ in
       modulePackages = mkOption {
         type = types.listOf types.package;
         default = [ ];
-        example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
+        example = literalExpression "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
         description = ''
           A list of global znc module packages to add to znc.
         '';
diff --git a/nixos/modules/services/networking/znc/options.nix b/nixos/modules/services/networking/znc/options.nix
index be9dc78c86d99..0db051126e86c 100644
--- a/nixos/modules/services/networking/znc/options.nix
+++ b/nixos/modules/services/networking/znc/options.nix
@@ -44,7 +44,7 @@ let
       modules = mkOption {
         type = types.listOf types.str;
         default = [ "simple_away" ];
-        example = literalExample ''[ "simple_away" "sasl" ]'';
+        example = literalExpression ''[ "simple_away" "sasl" ]'';
         description = ''
           ZNC network modules to load.
         '';
@@ -148,7 +148,7 @@ in
           description = ''
             IRC networks to connect the user to.
           '';
-          example = literalExample ''
+          example = literalExpression ''
             {
               "libera" = {
                 server = "irc.libera.chat";
@@ -170,7 +170,7 @@ in
         };
 
         passBlock = mkOption {
-          example = literalExample ''
+          example = ''
             &lt;Pass password&gt;
                Method = sha256
                Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index d2b36d9e75415..53091d8e2a0e0 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -270,7 +270,7 @@ in
       drivers = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "with pkgs; [ gutenprint hplip splix ]";
+        example = literalExpression "with pkgs; [ gutenprint hplip splix ]";
         description = ''
           CUPS drivers to use. Drivers provided by CUPS, cups-filters,
           Ghostscript and Samba are added unconditionally. If this list contains
diff --git a/nixos/modules/services/scheduling/atd.nix b/nixos/modules/services/scheduling/atd.nix
index 37f6651ec4cf2..9bb0191ee469a 100644
--- a/nixos/modules/services/scheduling/atd.nix
+++ b/nixos/modules/services/scheduling/atd.nix
@@ -58,7 +58,9 @@ in
     security.pam.services.atd = {};
 
     users.users.atd =
-      { uid = config.ids.uids.atd;
+      {
+        uid = config.ids.uids.atd;
+        group = "atd";
         description = "atd user";
         home = "/var/empty";
       };
diff --git a/nixos/modules/services/scheduling/cron.nix b/nixos/modules/services/scheduling/cron.nix
index 3bc31832946b9..1fac54003cbb5 100644
--- a/nixos/modules/services/scheduling/cron.nix
+++ b/nixos/modules/services/scheduling/cron.nix
@@ -52,7 +52,7 @@ in
       systemCronJobs = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [ "* * * * *  test   ls -l / > /tmp/cronout 2>&1"
             "* * * * *  eelco  echo Hello World > /home/eelco/cronout"
           ]
@@ -93,7 +93,12 @@ in
 
     { services.cron.enable = mkDefault (allFiles != []); }
     (mkIf (config.services.cron.enable) {
-      security.wrappers.crontab.source = "${cronNixosPkg}/bin/crontab";
+      security.wrappers.crontab =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${cronNixosPkg}/bin/crontab";
+        };
       environment.systemPackages = [ cronNixosPkg ];
       environment.etc.crontab =
         { source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; }
diff --git a/nixos/modules/services/scheduling/fcron.nix b/nixos/modules/services/scheduling/fcron.nix
index 42bed21bf25bd..acaa995f73950 100644
--- a/nixos/modules/services/scheduling/fcron.nix
+++ b/nixos/modules/services/scheduling/fcron.nix
@@ -136,10 +136,13 @@ in
         owner = "fcron";
         group = "fcron";
         setgid = true;
+        setuid = false;
       };
       fcronsighup = {
         source = "${pkgs.fcron}/bin/fcronsighup";
+        owner = "root";
         group = "fcron";
+        setuid = true;
       };
     };
     systemd.services.fcron = {
diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix
index 1d7a28d5d2454..6df147be0c495 100644
--- a/nixos/modules/services/search/elasticsearch.nix
+++ b/nixos/modules/services/search/elasticsearch.nix
@@ -5,13 +5,13 @@ with lib;
 let
   cfg = config.services.elasticsearch;
 
+  es7 = builtins.compareVersions cfg.package.version "7" >= 0;
+
   esConfig = ''
     network.host: ${cfg.listenAddress}
     cluster.name: ${cfg.cluster_name}
-    ${lib.optionalString cfg.single_node ''
-      discovery.type: single-node
-      gateway.auto_import_dangling_indices: true
-    ''}
+    ${lib.optionalString cfg.single_node "discovery.type: single-node"}
+    ${lib.optionalString (cfg.single_node && es7) "gateway.auto_import_dangling_indices: true"}
 
     http.port: ${toString cfg.port}
     transport.port: ${toString cfg.tcp_port}
@@ -53,7 +53,7 @@ in
     package = mkOption {
       description = "Elasticsearch package to use.";
       default = pkgs.elasticsearch;
-      defaultText = "pkgs.elasticsearch";
+      defaultText = literalExpression "pkgs.elasticsearch";
       type = types.package;
     };
 
@@ -140,7 +140,7 @@ in
       description = "Extra elasticsearch plugins";
       default = [ ];
       type = types.listOf types.package;
-      example = lib.literalExample "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
+      example = lib.literalExpression "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
     };
 
   };
@@ -201,6 +201,13 @@ in
 
         if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
       '';
+      postStart = ''
+        # Make sure elasticsearch is up and running before dependents
+        # are started
+        while ! ${pkgs.curl}/bin/curl -sS -f http://localhost:${toString cfg.port} 2>/dev/null; do
+          sleep 1
+        done
+      '';
     };
 
     environment.systemPackages = [ cfg.package ];
diff --git a/nixos/modules/services/search/hound.nix b/nixos/modules/services/search/hound.nix
index 7a44489efe61f..ef62175b0a3e3 100644
--- a/nixos/modules/services/search/hound.nix
+++ b/nixos/modules/services/search/hound.nix
@@ -50,7 +50,7 @@ in {
 
       package = mkOption {
         default = pkgs.hound;
-        defaultText = "pkgs.hound";
+        defaultText = literalExpression "pkgs.hound";
         type = types.package;
         description = ''
           Package for running hound.
@@ -63,16 +63,18 @@ in {
           The full configuration of the Hound daemon. Note the dbpath
           should be an absolute path to a writable location on disk.
         '';
-        example = ''
-          {
-             "max-concurrent-indexers" : 2,
-             "dbpath" : "''${services.hound.home}/data",
-             "repos" : {
-                "nixpkgs": {
-                   "url" : "https://www.github.com/NixOS/nixpkgs.git"
-                }
-             }
-          }
+        example = literalExpression ''
+          '''
+            {
+              "max-concurrent-indexers" : 2,
+              "dbpath" : "''${services.hound.home}/data",
+              "repos" : {
+                  "nixpkgs": {
+                    "url" : "https://www.github.com/NixOS/nixpkgs.git"
+                  }
+              }
+            }
+          '''
         '';
       };
 
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
index 2beb265ee5d11..381f5156ceb6d 100644
--- a/nixos/modules/services/search/kibana.nix
+++ b/nixos/modules/services/search/kibana.nix
@@ -149,8 +149,7 @@ in {
     package = mkOption {
       description = "Kibana package to use";
       default = pkgs.kibana;
-      defaultText = "pkgs.kibana";
-      example = "pkgs.kibana";
+      defaultText = literalExpression "pkgs.kibana";
       type = types.package;
     };
 
@@ -199,10 +198,12 @@ in {
     environment.systemPackages = [ cfg.package ];
 
     users.users.kibana = {
-      uid = config.ids.uids.kibana;
+      isSystemUser = true;
       description = "Kibana service user";
       home = cfg.dataDir;
       createHome = true;
+      group = "kibana";
     };
+    users.groups.kibana = {};
   };
 }
diff --git a/nixos/modules/services/search/meilisearch.md b/nixos/modules/services/search/meilisearch.md
new file mode 100644
index 0000000000000..98e7c542cb9af
--- /dev/null
+++ b/nixos/modules/services/search/meilisearch.md
@@ -0,0 +1,39 @@
+# Meilisearch {#module-services-meilisearch}
+
+Meilisearch is a lightweight, fast and powerful search engine. Think elastic search with a much smaller footprint.
+
+## Quickstart
+
+the minimum to start meilisearch is
+
+```nix
+services.meilisearch.enable = true;
+```
+
+this will start the http server included with meilisearch on port 7700.
+
+test with `curl -X GET 'http://localhost:7700/health'`
+
+## Usage
+
+you first need to add documents to an index before you can search for documents.
+
+### Add a documents to the `movies` index
+
+`curl -X POST 'http://127.0.0.1:7700/indexes/movies/documents' --data '[{"id": "123", "title": "Superman"}, {"id": 234, "title": "Batman"}]'`
+
+### Search documents in the `movies` index
+
+`curl 'http://127.0.0.1:7700/indexes/movies/search' --data '{ "q": "botman" }'` (note the typo is intentional and there to demonstrate the typo tolerant capabilities)
+
+## Defaults
+
+- The default nixos package doesn't come with the [dashboard](https://docs.meilisearch.com/learn/getting_started/quick_start.html#search), since the dashboard features makes some assets downloads at compile time.
+
+- Anonimized Analytics sent to meilisearch are disabled by default.
+
+- Default deployment is development mode. It doesn't require a secret master key. All routes are not protected and accessible.
+
+## Missing
+
+- the snapshot feature is not yet configurable from the module, it's just a matter of adding the relevant environment variables.
diff --git a/nixos/modules/services/search/meilisearch.nix b/nixos/modules/services/search/meilisearch.nix
new file mode 100644
index 0000000000000..f6210f6f16e1d
--- /dev/null
+++ b/nixos/modules/services/search/meilisearch.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.meilisearch;
+
+in
+{
+
+  meta.maintainers = with maintainers; [ Br1ght0ne happysalada ];
+  # Don't edit the docbook xml directly, edit the md and generate it:
+  # `pandoc meilisearch.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > meilisearch.xml`
+  meta.doc = ./meilisearch.xml;
+
+  ###### interface
+
+  options.services.meilisearch = {
+    enable = mkEnableOption "MeiliSearch - a RESTful search API";
+
+    package = mkOption {
+      description = "The package to use for meilisearch. Use this if you require specific features to be enabled. The default package has no features.";
+      default = pkgs.meilisearch;
+      defaultText = "pkgs.meilisearch";
+      type = types.package;
+    };
+
+    listenAddress = mkOption {
+      description = "MeiliSearch listen address.";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    listenPort = mkOption {
+      description = "MeiliSearch port to listen on.";
+      default = 7700;
+      type = types.port;
+    };
+
+    environment = mkOption {
+      description = "Defines the running environment of MeiliSearch.";
+      default = "development";
+      type = types.enum [ "development" "production" ];
+    };
+
+    # TODO change this to LoadCredentials once possible
+    masterKeyEnvironmentFile = mkOption {
+      description = ''
+        Path to file which contains the master key.
+        By doing so, all routes will be protected and will require a key to be accessed.
+        If no master key is provided, all routes can be accessed without requiring any key.
+        The format is the following:
+        MEILI_MASTER_KEY=my_secret_key
+      '';
+      default = null;
+      type = with types; nullOr path;
+    };
+
+    noAnalytics = mkOption {
+      description = ''
+        Deactivates analytics.
+        Analytics allow MeiliSearch to know how many users are using MeiliSearch,
+        which versions and which platforms are used.
+        This process is entirely anonymous.
+      '';
+      default = true;
+      type = types.bool;
+    };
+
+    logLevel = mkOption {
+      description = ''
+        Defines how much detail should be present in MeiliSearch's logs.
+        MeiliSearch currently supports four log levels, listed in order of increasing verbosity:
+        - 'ERROR': only log unexpected events indicating MeiliSearch is not functioning as expected
+        - 'WARN:' log all unexpected events, regardless of their severity
+        - 'INFO:' log all events. This is the default value
+        - 'DEBUG': log all events and including detailed information on MeiliSearch's internal processes.
+          Useful when diagnosing issues and debugging
+      '';
+      default = "INFO";
+      type = types.str;
+    };
+
+    maxIndexSize = mkOption {
+      description = ''
+        Sets the maximum size of the index.
+        Value must be given in bytes or explicitly stating a base unit.
+        For example, the default value can be written as 107374182400, '107.7Gb', or '107374 Mb'.
+        Default is 100 GiB
+      '';
+      default = "107374182400";
+      type = types.str;
+    };
+
+    payloadSizeLimit = mkOption {
+      description = ''
+        Sets the maximum size of accepted JSON payloads.
+        Value must be given in bytes or explicitly stating a base unit.
+        For example, the default value can be written as 107374182400, '107.7Gb', or '107374 Mb'.
+        Default is ~ 100 MB
+      '';
+      default = "104857600";
+      type = types.str;
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.meilisearch = {
+      description = "MeiliSearch daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = {
+        MEILI_DB_PATH = "/var/lib/meilisearch";
+        MEILI_HTTP_ADDR = "${cfg.listenAddress}:${toString cfg.listenPort}";
+        MEILI_NO_ANALYTICS = toString cfg.noAnalytics;
+        MEILI_ENV = cfg.environment;
+        MEILI_DUMPS_DIR = "/var/lib/meilisearch/dumps";
+        MEILI_LOG_LEVEL = cfg.logLevel;
+        MEILI_MAX_INDEX_SIZE = cfg.maxIndexSize;
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/meilisearch";
+        DynamicUser = true;
+        StateDirectory = "meilisearch";
+        EnvironmentFile = mkIf (cfg.masterKeyEnvironmentFile != null) cfg.masterKeyEnvironmentFile;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/search/meilisearch.xml b/nixos/modules/services/search/meilisearch.xml
new file mode 100644
index 0000000000000..c1a73f358c288
--- /dev/null
+++ b/nixos/modules/services/search/meilisearch.xml
@@ -0,0 +1,85 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-meilisearch">
+  <title>Meilisearch</title>
+  <para>
+    Meilisearch is a lightweight, fast and powerful search engine. Think
+    elastic search with a much smaller footprint.
+  </para>
+  <section xml:id="quickstart">
+    <title>Quickstart</title>
+    <para>
+      the minimum to start meilisearch is
+    </para>
+    <programlisting language="bash">
+services.meilisearch.enable = true;
+</programlisting>
+    <para>
+      this will start the http server included with meilisearch on port
+      7700.
+    </para>
+    <para>
+      test with
+      <literal>curl -X GET 'http://localhost:7700/health'</literal>
+    </para>
+  </section>
+  <section xml:id="usage">
+    <title>Usage</title>
+    <para>
+      you first need to add documents to an index before you can search
+      for documents.
+    </para>
+    <section xml:id="add-a-documents-to-the-movies-index">
+      <title>Add a documents to the <literal>movies</literal>
+      index</title>
+      <para>
+        <literal>curl -X POST 'http://127.0.0.1:7700/indexes/movies/documents' --data '[{&quot;id&quot;: &quot;123&quot;, &quot;title&quot;: &quot;Superman&quot;}, {&quot;id&quot;: 234, &quot;title&quot;: &quot;Batman&quot;}]'</literal>
+      </para>
+    </section>
+    <section xml:id="search-documents-in-the-movies-index">
+      <title>Search documents in the <literal>movies</literal>
+      index</title>
+      <para>
+        <literal>curl 'http://127.0.0.1:7700/indexes/movies/search' --data '{ &quot;q&quot;: &quot;botman&quot; }'</literal>
+        (note the typo is intentional and there to demonstrate the typo
+        tolerant capabilities)
+      </para>
+    </section>
+  </section>
+  <section xml:id="defaults">
+    <title>Defaults</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          The default nixos package doesn’t come with the
+          <link xlink:href="https://docs.meilisearch.com/learn/getting_started/quick_start.html#search">dashboard</link>,
+          since the dashboard features makes some assets downloads at
+          compile time.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Anonimized Analytics sent to meilisearch are disabled by
+          default.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Default deployment is development mode. It doesn’t require a
+          secret master key. All routes are not protected and
+          accessible.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="missing">
+    <title>Missing</title>
+    <itemizedlist spacing="compact">
+      <listitem>
+        <para>
+          the snapshot feature is not yet configurable from the module,
+          it’s just a matter of adding the relevant environment
+          variables.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
index a8615a20a1cf2..ea76bfc9298f6 100644
--- a/nixos/modules/services/search/solr.nix
+++ b/nixos/modules/services/search/solr.nix
@@ -16,7 +16,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.solr;
-        defaultText = "pkgs.solr";
+        defaultText = literalExpression "pkgs.solr";
         description = "Which Solr package to use.";
       };
 
diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix
index 94c0ba141179e..d302a4e000209 100644
--- a/nixos/modules/services/security/certmgr.nix
+++ b/nixos/modules/services/security/certmgr.nix
@@ -40,7 +40,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.certmgr;
-      defaultText = "pkgs.certmgr";
+      defaultText = literalExpression "pkgs.certmgr";
       description = "Which certmgr package to use in the service.";
     };
 
@@ -76,7 +76,7 @@ in
 
     specs = mkOption {
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
       {
         exampleCert =
         let
diff --git a/nixos/modules/services/security/cfssl.nix b/nixos/modules/services/security/cfssl.nix
index ee6d5d91fe155..e5bed0a9987c0 100644
--- a/nixos/modules/services/security/cfssl.nix
+++ b/nixos/modules/services/security/cfssl.nix
@@ -27,13 +27,13 @@ in {
     };
 
     ca = mkOption {
-      defaultText = "\${cfg.dataDir}/ca.pem";
+      defaultText = literalExpression ''"''${cfg.dataDir}/ca.pem"'';
       type = types.str;
       description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
     };
 
     caKey = mkOption {
-      defaultText = "file:\${cfg.dataDir}/ca-key.pem";
+      defaultText = literalExpression ''"file:''${cfg.dataDir}/ca-key.pem"'';
       type = types.str;
       description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
     };
diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index 499d346675096..67e1026dcef4d 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -55,22 +55,24 @@ in
 
       package = mkOption {
         default = pkgs.fail2ban;
+        defaultText = literalExpression "pkgs.fail2ban";
         type = types.package;
-        example = "pkgs.fail2ban_0_11";
+        example = literalExpression "pkgs.fail2ban_0_11";
         description = "The fail2ban package to use for running the fail2ban service.";
       };
 
       packageFirewall = mkOption {
         default = pkgs.iptables;
+        defaultText = literalExpression "pkgs.iptables";
         type = types.package;
-        example = "pkgs.nftables";
+        example = literalExpression "pkgs.nftables";
         description = "The firewall package used by fail2ban service.";
       };
 
       extraPackages = mkOption {
         default = [];
         type = types.listOf types.package;
-        example = lib.literalExample "[ pkgs.ipset ]";
+        example = lib.literalExpression "[ pkgs.ipset ]";
         description = ''
           Extra packages to be made available to the fail2ban service. The example contains
           the packages needed by the `iptables-ipset-proto6` action.
@@ -202,7 +204,7 @@ in
 
       jails = mkOption {
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           { apache-nohome-iptables = '''
               # Block an IP address if it accesses a non-existent
               # home directory more than 5 times in 10 minutes,
diff --git a/nixos/modules/services/security/fprintd.nix b/nixos/modules/services/security/fprintd.nix
index fe0fba5b45d76..87c3f1f6f9e42 100644
--- a/nixos/modules/services/security/fprintd.nix
+++ b/nixos/modules/services/security/fprintd.nix
@@ -23,7 +23,7 @@ in
       package = mkOption {
         type = types.package;
         default = fprintdPkg;
-        defaultText = "if cfg.tod.enable then pkgs.fprintd-tod else pkgs.fprintd";
+        defaultText = literalExpression "if config.services.fprintd.tod.enable then pkgs.fprintd-tod else pkgs.fprintd";
         description = ''
           fprintd package to use.
         '';
@@ -35,7 +35,7 @@ in
 
         driver = mkOption {
           type = types.package;
-          example = literalExample "pkgs.libfprint-2-tod1-goodix";
+          example = literalExpression "pkgs.libfprint-2-tod1-goodix";
           description = ''
             Touch OEM Drivers (TOD) package to use.
           '';
diff --git a/nixos/modules/services/security/haka.nix b/nixos/modules/services/security/haka.nix
index 618e689924fd6..2cfc05f3033bb 100644
--- a/nixos/modules/services/security/haka.nix
+++ b/nixos/modules/services/security/haka.nix
@@ -59,7 +59,7 @@ in
 
       package = mkOption {
         default = pkgs.haka;
-        defaultText = "pkgs.haka";
+        defaultText = literalExpression "pkgs.haka";
         type = types.package;
         description = "
           Which Haka derivation to use.
diff --git a/nixos/modules/services/security/hockeypuck.nix b/nixos/modules/services/security/hockeypuck.nix
index 686634c8add83..d0e152934f508 100644
--- a/nixos/modules/services/security/hockeypuck.nix
+++ b/nixos/modules/services/security/hockeypuck.nix
@@ -18,7 +18,7 @@ in {
     settings = lib.mkOption {
       type = settingsFormat.type;
       default = { };
-      example = lib.literalExample ''
+      example = lib.literalExpression ''
         {
           hockeypuck = {
             loglevel = "INFO";
@@ -82,8 +82,10 @@ in {
 
     users.users.hockeypuck = {
       isSystemUser = true;
+      group = "hockeypuck";
       description = "Hockeypuck user";
     };
+    users.groups.hockeypuck = {};
 
     systemd.services.hockeypuck = {
       description = "Hockeypuck OpenPGP Key Server";
diff --git a/nixos/modules/services/security/nginx-sso.nix b/nixos/modules/services/security/nginx-sso.nix
index 50d250fc4d761..b4de1d36edd8d 100644
--- a/nixos/modules/services/security/nginx-sso.nix
+++ b/nixos/modules/services/security/nginx-sso.nix
@@ -13,7 +13,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.nginx-sso;
-      defaultText = "pkgs.nginx-sso";
+      defaultText = literalExpression "pkgs.nginx-sso";
       description = ''
         The nginx-sso package that should be used.
       '';
@@ -22,7 +22,7 @@ in {
     configuration = mkOption {
       type = types.attrsOf types.unspecified;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           listen = { addr = "127.0.0.1"; port = 8080; };
 
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index e85fd4b75df4f..4d35624241708 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -91,7 +91,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.oauth2-proxy;
-      defaultText = "pkgs.oauth2-proxy";
+      defaultText = literalExpression "pkgs.oauth2-proxy";
       description = ''
         The package that provides oauth2-proxy.
       '';
diff --git a/nixos/modules/services/security/opensnitch.nix b/nixos/modules/services/security/opensnitch.nix
new file mode 100644
index 0000000000000..919346cf2bb11
--- /dev/null
+++ b/nixos/modules/services/security/opensnitch.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  name = "opensnitch";
+  cfg = config.services.opensnitch;
+in {
+  options = {
+    services.opensnitch = {
+      enable = mkEnableOption "Opensnitch application firewall";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      packages = [ pkgs.opensnitch ];
+      services.opensnitchd.wantedBy = [ "multi-user.target" ];
+    };
+
+  };
+}
+
diff --git a/nixos/modules/services/security/physlock.nix b/nixos/modules/services/security/physlock.nix
index da5c22a90a095..760e80f147f7a 100644
--- a/nixos/modules/services/security/physlock.nix
+++ b/nixos/modules/services/security/physlock.nix
@@ -38,9 +38,6 @@ in
           setuid wrapper to allow any user to start physlock as root, which
           is a minor security risk. Call the physlock binary to use this instead
           of using the systemd service.
-
-          Note that you might need to relog to have the correct binary in your
-          PATH upon changing this option.
         '';
       };
 
@@ -129,7 +126,12 @@ in
 
     (mkIf cfg.allowAnyUser {
 
-      security.wrappers.physlock = { source = "${pkgs.physlock}/bin/physlock"; user = "root"; };
+      security.wrappers.physlock =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.physlock}/bin/physlock";
+        };
 
     })
   ]);
diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
index 63271848e9431..05f4995cc4163 100644
--- a/nixos/modules/services/security/privacyidea.nix
+++ b/nixos/modules/services/security/privacyidea.nix
@@ -169,7 +169,6 @@ in
 
         configFile = mkOption {
           type = types.path;
-          default = "";
           description = ''
             Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
           '';
@@ -228,7 +227,7 @@ in
         path = with pkgs; [ openssl ];
         environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
         preStart = let
-          pi-manage = "${pkgs.sudo}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
+          pi-manage = "${config.security.sudo.package}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
           pgsu = config.services.postgresql.superUser;
           psql = config.services.postgresql.package;
         in ''
@@ -239,8 +238,8 @@ in
                                                    -i "${piCfgFile}"
           chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg
           if ! test -e "${cfg.stateDir}/db-created"; then
-            ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
-            ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
+            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
+            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
             ${pi-manage} create_enckey
             ${pi-manage} create_audit_keys
             ${pi-manage} createdb
@@ -273,7 +272,7 @@ in
     (mkIf cfg.ldap-proxy.enable {
 
       systemd.services.privacyidea-ldap-proxy = let
-        ldap-proxy-env = pkgs.python2.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
+        ldap-proxy-env = pkgs.python3.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
       in {
         description = "privacyIDEA LDAP proxy";
         wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix
index 5908f727d5355..fea2a855e20f0 100644
--- a/nixos/modules/services/security/shibboleth-sp.nix
+++ b/nixos/modules/services/security/shibboleth-sp.nix
@@ -14,7 +14,7 @@ in {
 
       configFile = mkOption {
         type = types.path;
-        example = "${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml";
+        example = literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"'';
         description = "Path to shibboleth config file";
       };
 
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index a91060dc659a7..f4911597564b6 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -23,7 +23,7 @@ in {
 
       package = mkOption {
         default = pkgs.sks;
-        defaultText = "pkgs.sks";
+        defaultText = literalExpression "pkgs.sks";
         type = types.package;
         description = "Which SKS derivation to use.";
       };
@@ -74,7 +74,7 @@ in {
       webroot = mkOption {
         type = types.nullOr types.path;
         default = "${sksPkg.webSamples}/OpenPKG";
-        defaultText = "\${pkgs.sks.webSamples}/OpenPKG";
+        defaultText = literalExpression ''"''${package.webSamples}/OpenPKG"'';
         description = ''
           Source directory (will be symlinked, if not null) for the files the
           built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
diff --git a/nixos/modules/services/security/step-ca.nix b/nixos/modules/services/security/step-ca.nix
index 64eee11f58805..db7f81acd2a39 100644
--- a/nixos/modules/services/security/step-ca.nix
+++ b/nixos/modules/services/security/step-ca.nix
@@ -13,6 +13,7 @@ in
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.step-ca;
+        defaultText = lib.literalExpression "pkgs.step-ca";
         description = "Which step-ca package to use.";
       };
       address = lib.mkOption {
@@ -118,7 +119,7 @@ in
           ];
 
           # ProtectProc = "invisible"; # not supported by upstream yet
-          # ProcSubset = "pid"; # not supported by upstream upstream yet
+          # ProcSubset = "pid"; # not supported by upstream yet
           # PrivateUsers = true; # doesn't work with privileged ports therefore not supported by upstream
 
           DynamicUser = true;
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 9e8f18e93c85b..c3e3248ee8ab1 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -232,8 +232,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.tor;
-        defaultText = "pkgs.tor";
-        example = literalExample "pkgs.tor";
+        defaultText = literalExpression "pkgs.tor";
         description = "Tor package to use.";
       };
 
@@ -1013,6 +1012,7 @@ in
         # Tor cannot currently bind privileged port when PrivateUsers=true,
         # see https://gitlab.torproject.org/legacy/trac/-/issues/20930
         PrivateUsers = !bindsPrivilegedPort;
+        ProcSubset = "pid";
         ProtectClock = true;
         ProtectControlGroups = true;
         ProtectHome = true;
@@ -1020,9 +1020,10 @@ in
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
+        ProtectProc = "invisible";
         ProtectSystem = "strict";
         RemoveIPC = true;
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
         RestrictNamespaces = true;
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index 4cdb3a041b59d..201b37f17ba58 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -44,7 +44,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.usbguard;
-        defaultText = "pkgs.usbguard";
+        defaultText = literalExpression "pkgs.usbguard";
         description = ''
           The usbguard package to use. If you do not need the Qt GUI, use
           <literal>pkgs.usbguard-nox</literal> to save disk space.
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index 5a20f6413b1b7..b0ade62d97c9b 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -42,7 +42,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.vault;
-        defaultText = "pkgs.vault";
+        defaultText = literalExpression "pkgs.vault";
         description = "This option specifies the vault package to use.";
       };
 
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index d28ea61e66aa1..5b951bc85ec0a 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -60,7 +60,7 @@ in {
     config = mkOption {
       type = attrsOf (nullOr (oneOf [ bool int str ]));
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           domain = "https://bw.domain.tld:8443";
           signupsAllowed = true;
@@ -106,14 +106,14 @@ in {
     package = mkOption {
       type = package;
       default = pkgs.vaultwarden;
-      defaultText = "pkgs.vaultwarden";
+      defaultText = literalExpression "pkgs.vaultwarden";
       description = "Vaultwarden package to use.";
     };
 
     webVaultPackage = mkOption {
       type = package;
       default = pkgs.vaultwarden-vault;
-      defaultText = "pkgs.vaultwarden-vault";
+      defaultText = literalExpression "pkgs.vaultwarden-vault";
       description = "Web vault package to use.";
     };
   };
diff --git a/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix
index 2972c64a36413..8be2457e1e2f2 100644
--- a/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixos/modules/services/security/yubikey-agent.nix
@@ -13,7 +13,7 @@ in
 {
   ###### interface
 
-  meta.maintainers = with maintainers; [ philandstuff rawkode ];
+  meta.maintainers = with maintainers; [ philandstuff rawkode jwoudenberg ];
 
   options = {
 
@@ -33,7 +33,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.yubikey-agent;
-        defaultText = "pkgs.yubikey-agent";
+        defaultText = literalExpression "pkgs.yubikey-agent";
         description = ''
           The package used for the yubikey-agent daemon.
         '';
@@ -49,6 +49,12 @@ in
     # yubikey-agent package
     systemd.user.services.yubikey-agent = mkIf (pinentryFlavor != null) {
       path = [ pkgs.pinentry.${pinentryFlavor} ];
+      wantedBy = [
+        (if pinentryFlavor == "tty" || pinentryFlavor == "curses" then
+          "default.target"
+        else
+          "graphical-session.target")
+      ];
     };
 
     environment.extraInit = ''
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index e29bdbe264cc8..452efc736439b 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -106,6 +106,7 @@ in
       path = optional ecfg.enableNotifications pkgs.dbus;
       serviceConfig = {
         StandardOutput = "null";
+        StandardError = "journal";
         ExecStart = ''
           ${pkgs.earlyoom}/bin/earlyoom \
           -m ${toString ecfg.freeMemThreshold} \
diff --git a/nixos/modules/services/system/kerberos/heimdal.nix b/nixos/modules/services/system/kerberos/heimdal.nix
index f0e56c7951a49..837c59caa5620 100644
--- a/nixos/modules/services/system/kerberos/heimdal.nix
+++ b/nixos/modules/services/system/kerberos/heimdal.nix
@@ -27,7 +27,7 @@ in
 {
   # No documentation about correct triggers, so guessing at them.
 
-  config = mkIf (cfg.enable && kerberos == pkgs.heimdalFull) {
+  config = mkIf (cfg.enable && kerberos == pkgs.heimdal) {
     systemd.services.kadmind = {
       description = "Kerberos Administration Daemon";
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix
index bb99e5e36ff8b..8f23454af9dfd 100644
--- a/nixos/modules/services/system/localtime.nix
+++ b/nixos/modules/services/system/localtime.nix
@@ -37,7 +37,9 @@ in {
     users.users.localtimed = {
       description = "localtime daemon";
       isSystemUser = true;
+      group = "localtimed";
     };
+    users.groups.localtimed = {};
 
     systemd.services.localtime = {
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/system/saslauthd.nix b/nixos/modules/services/system/saslauthd.nix
index 8fcf4fb91fc46..466b0ca60a7e6 100644
--- a/nixos/modules/services/system/saslauthd.nix
+++ b/nixos/modules/services/system/saslauthd.nix
@@ -20,7 +20,7 @@ in
 
       package = mkOption {
         default = pkgs.cyrus_sasl.bin;
-        defaultText = "pkgs.cyrus_sasl.bin";
+        defaultText = literalExpression "pkgs.cyrus_sasl.bin";
         type = types.package;
         description = "Cyrus SASL package to use.";
       };
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index 151a1dd638d19..cb0da9e83b42e 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -50,7 +50,7 @@ in {
         config = mkOption {
           type = types.attrs;
           default = {};
-          example = literalExample ''
+          example = literalExpression ''
             {
               download_location = "/srv/torrents/";
               max_upload_speed = "1000.0";
@@ -149,7 +149,7 @@ in {
 
         package = mkOption {
           type = types.package;
-          example = literalExample "pkgs.deluge-2_x";
+          example = literalExpression "pkgs.deluge-2_x";
           description = ''
             Deluge package to use.
           '';
diff --git a/nixos/modules/services/torrent/flexget.nix b/nixos/modules/services/torrent/flexget.nix
index 6ac85f8fa1782..e500e02d861b2 100644
--- a/nixos/modules/services/torrent/flexget.nix
+++ b/nixos/modules/services/torrent/flexget.nix
@@ -39,7 +39,7 @@ in {
 
       systemScheduler = mkOption {
         default = true;
-        example = "false";
+        example = false;
         type = types.bool;
         description = "When true, execute the runs via the flexget-runner.timer. If false, you have to specify the settings yourself in the YML file.";
       };
diff --git a/nixos/modules/services/torrent/magnetico.nix b/nixos/modules/services/torrent/magnetico.nix
index 7465c10e002c5..3dd7b1ece768b 100644
--- a/nixos/modules/services/torrent/magnetico.nix
+++ b/nixos/modules/services/torrent/magnetico.nix
@@ -111,7 +111,7 @@ in {
     web.credentials = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      example = lib.literalExample ''
+      example = lib.literalExpression ''
         {
           myuser = "$2y$12$YE01LZ8jrbQbx6c0s2hdZO71dSjn2p/O9XsYJpz.5968yCysUgiaG";
         }
@@ -172,8 +172,10 @@ in {
 
     users.users.magnetico = {
       description = "Magnetico daemons user";
+      group = "magnetico";
       isSystemUser = true;
     };
+    users.groups.magnetico = {};
 
     systemd.services.magneticod = {
       description = "Magnetico DHT crawler";
diff --git a/nixos/modules/services/torrent/opentracker.nix b/nixos/modules/services/torrent/opentracker.nix
index 74f443381d929..d76d61dfe8594 100644
--- a/nixos/modules/services/torrent/opentracker.nix
+++ b/nixos/modules/services/torrent/opentracker.nix
@@ -13,7 +13,7 @@ in {
         opentracker package to use
       '';
       default = pkgs.opentracker;
-      defaultText = "pkgs.opentracker";
+      defaultText = literalExpression "pkgs.opentracker";
     };
 
     extraOptions = mkOption {
diff --git a/nixos/modules/services/torrent/peerflix.nix b/nixos/modules/services/torrent/peerflix.nix
index a74f65984328d..3e5f80960dc7a 100644
--- a/nixos/modules/services/torrent/peerflix.nix
+++ b/nixos/modules/services/torrent/peerflix.nix
@@ -60,6 +60,10 @@ in {
       };
     };
 
-    users.users.peerflix.uid = config.ids.uids.peerflix;
+    users.users.peerflix = {
+      isSystemUser = true;
+      group = "peerflix";
+    };
+    users.groups.peerflix = {};
   };
 }
diff --git a/nixos/modules/services/torrent/rtorrent.nix b/nixos/modules/services/torrent/rtorrent.nix
index be57c03b17216..dd7df623c7391 100644
--- a/nixos/modules/services/torrent/rtorrent.nix
+++ b/nixos/modules/services/torrent/rtorrent.nix
@@ -45,7 +45,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.rtorrent;
-      defaultText = "pkgs.rtorrent";
+      defaultText = literalExpression "pkgs.rtorrent";
       description = ''
         The rtorrent package to use.
       '';
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 34a5219c95947..a6ff467914a10 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -7,15 +7,20 @@ let
   inherit (config.environment) etc;
   apparmor = config.security.apparmor;
   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);
+  settingsFormat = pkgs.formats.json {};
+  settingsFile = settingsFormat.generate "settings.json" cfg.settings;
 in
 {
+  imports = [
+    (mkRenamedOptionModule ["services" "transmission" "port"]
+                           ["services" "transmission" "settings" "rpc-port"])
+    (mkAliasOptionModule ["services" "transmission" "openFirewall"]
+                         ["services" "transmission" "openPeerPorts"])
+  ];
   options = {
     services.transmission = {
       enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
@@ -24,48 +29,141 @@ in
         transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
         or other clients like stig or tremc.
 
-        Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
+        Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
         accessible to users in the "transmission" group'';
 
-      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 = "${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 =
-          {
-            download-dir = "/srv/torrents/";
-            incomplete-dir = "/srv/torrents/.incomplete/";
-            incomplete-dir-enabled = true;
-            rpc-whitelist = "127.0.0.1,192.168.*.*";
-          };
+      settings = mkOption {
         description = ''
-          Attribute set whose fields overwrites fields in
+          Settings whose options overwrite 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.
+          (each time the service starts).
 
           See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
-          for documentation.
+          for documentation of settings not explicitely covered by this module.
         '';
+        default = {};
+        type = types.submodule {
+          freeformType = settingsFormat.type;
+          options.download-dir = mkOption {
+            type = types.path;
+            default = "${cfg.home}/${downloadsDir}";
+            description = "Directory where to download torrents.";
+          };
+          options.incomplete-dir = mkOption {
+            type = types.path;
+            default = "${cfg.home}/${incompleteDir}";
+            description = ''
+              When enabled with
+              services.transmission.home
+              <xref linkend="opt-services.transmission.settings.incomplete-dir-enabled"/>,
+              new torrents will download the files to this directory.
+              When complete, the files will be moved to download-dir
+              <xref linkend="opt-services.transmission.settings.download-dir"/>.
+            '';
+          };
+          options.incomplete-dir-enabled = mkOption {
+            type = types.bool;
+            default = true;
+            description = "";
+          };
+          options.message-level = mkOption {
+            type = types.ints.between 0 2;
+            default = 2;
+            description = "Set verbosity of transmission messages.";
+          };
+          options.peer-port = mkOption {
+            type = types.port;
+            default = 51413;
+            description = "The peer port to listen for incoming connections.";
+          };
+          options.peer-port-random-high = mkOption {
+            type = types.port;
+            default = 65535;
+            description = ''
+              The maximum peer port to listen to for incoming connections
+              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
+            '';
+          };
+          options.peer-port-random-low = mkOption {
+            type = types.port;
+            default = 65535;
+            description = ''
+              The minimal peer port to listen to for incoming connections
+              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
+            '';
+          };
+          options.peer-port-random-on-start = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Randomize the peer port.";
+          };
+          options.rpc-bind-address = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            example = "0.0.0.0";
+            description = ''
+              Where to listen for RPC connections.
+              Use \"0.0.0.0\" to listen on all interfaces.
+            '';
+          };
+          options.rpc-port = mkOption {
+            type = types.port;
+            default = 9091;
+            description = "The RPC port to listen to.";
+          };
+          options.script-torrent-done-enabled = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Whether to run
+              <xref linkend="opt-services.transmission.settings.script-torrent-done-filename"/>
+              at torrent completion.
+            '';
+          };
+          options.script-torrent-done-filename = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = "Executable to be run at torrent completion.";
+          };
+          options.umask = mkOption {
+            type = types.int;
+            default = 2;
+            description = ''
+              Sets transmission's file mode creation mask.
+              See the umask(2) manpage for more information.
+              Users who want their saved torrents to be world-writable
+              may want to set this value to 0.
+              Bear in mind that the json markup language only accepts numbers in base 10,
+              so the standard umask(2) octal notation "022" is written in settings.json as 18.
+            '';
+          };
+          options.utp-enabled = mkOption {
+            type = types.bool;
+            default = true;
+            description = ''
+              Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
+            '';
+          };
+          options.watch-dir = mkOption {
+            type = types.path;
+            default = "${cfg.home}/${watchDir}";
+            description = "Watch a directory for torrent files and add them to transmission.";
+          };
+          options.watch-dir-enabled = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''Whether to enable the
+              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
+            '';
+          };
+          options.trash-original-torrent-files = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''Whether to delete torrents added from the
+              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
+            '';
+          };
+        };
       };
 
       downloadDirPermissions = mkOption {
@@ -74,31 +172,22 @@ in
         example = "775";
         description = ''
           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>.
+          on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>
+          and <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
           Note that you may also want to change
-          <link linkend="opt-services.transmission.settings">settings.umask</link>.
-        '';
-      };
-
-      port = mkOption {
-        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>.
+          <xref linkend="opt-services.transmission.settings.umask"/>.
         '';
       };
 
       home = mkOption {
         type = types.path;
-        default = homeDir;
+        default = "/var/lib/transmission";
         description = ''
           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.
+          as well as <literal>${downloadsDir}/</literal> unless
+          <xref linkend="opt-services.transmission.settings.download-dir"/> is changed,
+          and <literal>${incompleteDir}/</literal> unless
+          <xref linkend="opt-services.transmission.settings.incomplete-dir"/> is changed.
         '';
       };
 
@@ -119,19 +208,30 @@ in
         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>.
+          to set secret config parameters like <code>rpc-password</code>.
         '';
         default = "/dev/null";
         example = "/var/lib/secrets/transmission/settings.json";
       };
 
-      openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--log-debug" ];
+        description = ''
+          Extra flags passed to the transmission command in the service definition.
+        '';
+      };
+
+      openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
+
+      openRPCPort = mkEnableOption "opening of the RPC port 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>.
+        <code>peer-limit-global"</code>.
         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'';
@@ -152,36 +252,10 @@ in
       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}'
+      '' + optionalString cfg.settings.watch-dir-enabled ''
+      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-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.enable "apparmor.service";
@@ -197,15 +271,13 @@ in
           install -D -m 600 -o '${cfg.user}' -g '${cfg.group}' /dev/stdin \
            '${cfg.home}/${settingsDir}/settings.json'
         '')];
-        ExecStart="${pkgs.transmission}/bin/transmission-daemon -f -g ${cfg.home}/${settingsDir}";
+        ExecStart="${pkgs.transmission}/bin/transmission-daemon -f -g ${cfg.home}/${settingsDir} ${escapeShellArgs cfg.extraFlags}";
         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";
@@ -226,11 +298,9 @@ in
             cfg.settings.download-dir
           ] ++
           optional cfg.settings.incomplete-dir-enabled
-            cfg.settings.incomplete-dir
-          ++
-          optional cfg.settings.watch-dir-enabled
-            cfg.settings.watch-dir
-          ;
+            cfg.settings.incomplete-dir ++
+          optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
+            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.
@@ -239,8 +309,10 @@ in
           "/run"
           ] ++
           optional (cfg.settings.script-torrent-done-enabled &&
-                    cfg.settings.script-torrent-done-filename != "")
-            cfg.settings.script-torrent-done-filename;
+                    cfg.settings.script-torrent-done-filename != null)
+            cfg.settings.script-torrent-done-filename ++
+          optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
+            cfg.settings.watch-dir;
         # The following options are only for optimizing:
         # systemd-analyze security transmission
         AmbientCapabilities = "";
@@ -285,7 +357,6 @@ in
           "quotactl"
         ];
         SystemCallArchitectures = "native";
-        SystemCallErrorNumber = "EPERM";
       };
     };
 
@@ -307,25 +378,28 @@ in
       };
     });
 
-    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 ];
-        }
-    );
+    networking.firewall = mkMerge [
+      (mkIf cfg.openPeerPorts (
+        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 ];
+          }
+      ))
+      (mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
+    ];
 
     boot.kernel.sysctl = mkMerge [
       # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
@@ -340,21 +414,21 @@ in
         # 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";
+        "net.ipv4.ip_local_port_range" = mkDefault "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;
+        "net.netfilter.nf_conntrack_generic_timeout" = mkDefault 60;
         # Timeout faster established but inactive connections.
         # Usual default is 432000.
-        "net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
+        "net.netfilter.nf_conntrack_tcp_timeout_established" = mkDefault 600;
         # Clear immediately TCP states after timeout.
         # Usual default is 120.
-        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
+        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = mkDefault 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;
+        "net.netfilter.nf_conntrack_max" = mkDefault 1048576;
       })
     ];
 
@@ -370,7 +444,7 @@ in
         rw ${cfg.settings.incomplete-dir}/**,
       ''}
       ${optionalString cfg.settings.watch-dir-enabled ''
-        rw ${cfg.settings.watch-dir}/**,
+        r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
       ''}
       profile dirs {
         rw ${cfg.settings.download-dir}/**,
@@ -378,12 +452,12 @@ in
           rw ${cfg.settings.incomplete-dir}/**,
         ''}
         ${optionalString cfg.settings.watch-dir-enabled ''
-          rw ${cfg.settings.watch-dir}/**,
+          r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
         ''}
       }
 
       ${optionalString (cfg.settings.script-torrent-done-enabled &&
-                        cfg.settings.script-torrent-done-filename != "") ''
+                        cfg.settings.script-torrent-done-filename != null) ''
         # 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=
diff --git a/nixos/modules/services/ttys/getty.nix b/nixos/modules/services/ttys/getty.nix
index 7cf2ff87da262..7021a2c80f857 100644
--- a/nixos/modules/services/ttys/getty.nix
+++ b/nixos/modules/services/ttys/getty.nix
@@ -24,6 +24,7 @@ in
 
   imports = [
     (mkRenamedOptionModule [ "services" "mingetty" ] [ "services" "getty" ])
+    (mkRemovedOptionModule [ "services" "getty" "serialSpeed" ] ''set non-standard baudrates with `boot.kernelParams` i.e. boot.kernelParams = ["console=ttyS2,1500000"];'')
   ];
 
   options = {
@@ -42,6 +43,7 @@ in
       loginProgram = mkOption {
         type = types.path;
         default = "${pkgs.shadow}/bin/login";
+        defaultText = literalExpression ''"''${pkgs.shadow}/bin/login"'';
         description = ''
           Path to the login binary executed by agetty.
         '';
@@ -91,17 +93,6 @@ in
         '';
       };
 
-      serialSpeed = mkOption {
-        type = types.listOf types.int;
-        default = [ 115200 57600 38400 9600 ];
-        example = [ 38400 9600 ];
-        description = ''
-            Bitrates to allow for agetty's listening on serial ports. Listing more
-            bitrates gives more interoperability but at the cost of long delays
-            for getting a sync on the line.
-        '';
-      };
-
     };
 
   };
@@ -123,10 +114,17 @@ in
       };
 
     systemd.services."serial-getty@" =
-      let speeds = concatStringsSep "," (map toString config.services.getty.serialSpeed); in
       { serviceConfig.ExecStart = [
           "" # override upstream default with an empty ExecStart
-          (gettyCmd "%I --keep-baud ${speeds} $TERM")
+          (gettyCmd "%I --keep-baud $TERM")
+        ];
+        restartIfChanged = false;
+      };
+
+    systemd.services."autovt@" =
+      { serviceConfig.ExecStart = [
+          "" # override upstream default with an empty ExecStart
+          (gettyCmd "--noclear %I $TERM")
         ];
         restartIfChanged = false;
       };
diff --git a/nixos/modules/services/video/epgstation/default.nix b/nixos/modules/services/video/epgstation/default.nix
index b13393c8983ad..36f7b937d5acd 100644
--- a/nixos/modules/services/video/epgstation/default.nix
+++ b/nixos/modules/services/video/epgstation/default.nix
@@ -33,7 +33,7 @@ let
     fi
   '';
 
-  streamingConfig = builtins.fromJSON (builtins.readFile ./streaming.json);
+  streamingConfig = lib.importJSON ./streaming.json;
   logConfig = {
     appenders.stdout.type = "stdout";
     categories = {
@@ -126,6 +126,7 @@ in
       passwordFile = mkOption {
         type = types.path;
         default = pkgs.writeText "epgstation-password" defaultPassword;
+        defaultText = literalDocBook ''a file containing <literal>${defaultPassword}</literal>'';
         example = "/run/keys/epgstation-password";
         description = ''
           A file containing the password for <option>basicAuth.user</option>.
@@ -145,6 +146,7 @@ in
       passwordFile = mkOption {
         type = types.path;
         default = pkgs.writeText "epgstation-db-password" defaultPassword;
+        defaultText = literalDocBook ''a file containing <literal>${defaultPassword}</literal>'';
         example = "/run/keys/epgstation-db-password";
         description = ''
           A file containing the password for the database named
@@ -189,14 +191,33 @@ in
           type = with types; listOf attrs;
           description = "Encoding presets for recorded videos.";
           default = [
-            { name = "H264";
+            {
+              name = "H264";
               cmd = "${pkgs.epgstation}/libexec/enc.sh main";
               suffix = ".mp4";
-              default = true; }
-            { name = "H264-sub";
+              default = true;
+            }
+            {
+              name = "H264-sub";
               cmd = "${pkgs.epgstation}/libexec/enc.sh sub";
-              suffix = "-sub.mp4"; }
+              suffix = "-sub.mp4";
+            }
           ];
+          defaultText = literalExpression ''
+            [
+              {
+                name = "H264";
+                cmd = "''${pkgs.epgstation}/libexec/enc.sh main";
+                suffix = ".mp4";
+                default = true;
+              }
+              {
+                name = "H264-sub";
+                cmd = "''${pkgs.epgstation}/libexec/enc.sh sub";
+                suffix = "-sub.mp4";
+              }
+            ]
+          '';
         };
       };
     };
diff --git a/nixos/modules/services/video/mirakurun.nix b/nixos/modules/services/video/mirakurun.nix
index 1a99d1c976923..16efb56cfd610 100644
--- a/nixos/modules/services/video/mirakurun.nix
+++ b/nixos/modules/services/video/mirakurun.nix
@@ -72,7 +72,7 @@ in
         serverSettings = mkOption {
           type = settingsFmt.type;
           default = {};
-          example = literalExample ''
+          example = literalExpression ''
             {
               highWaterMark = 25165824;
               overflowTimeLimit = 30000;
@@ -89,7 +89,7 @@ in
         tunerSettings = mkOption {
           type = with types; nullOr settingsFmt.type;
           default = null;
-          example = literalExample ''
+          example = literalExpression ''
             [
               {
                 name = "tuner-name";
@@ -110,7 +110,7 @@ in
         channelSettings = mkOption {
           type = with types; nullOr settingsFmt.type;
           default = null;
-          example = literalExample ''
+          example = literalExpression ''
             [
               {
                 name = "channel";
diff --git a/nixos/modules/services/video/replay-sorcery.nix b/nixos/modules/services/video/replay-sorcery.nix
index d78e782c7968c..abe7202a4a862 100644
--- a/nixos/modules/services/video/replay-sorcery.nix
+++ b/nixos/modules/services/video/replay-sorcery.nix
@@ -26,7 +26,7 @@ in
         type = attrsOf (oneOf [ str int ]);
         default = {};
         description = "System-wide configuration for ReplaySorcery (/etc/replay-sorcery.conf).";
-        example = literalExample ''
+        example = literalExpression ''
           {
             videoInput = "hwaccel"; # requires `services.replay-sorcery.enableSysAdminCapability = true`
             videoFramerate = 60;
@@ -44,8 +44,10 @@ in
 
     security.wrappers = mkIf cfg.enableSysAdminCapability {
       replay-sorcery = {
-        source = "${pkgs.replay-sorcery}/bin/replay-sorcery";
+        owner = "root";
+        group = "root";
         capabilities = "cap_sys_admin+ep";
+        source = "${pkgs.replay-sorcery}/bin/replay-sorcery";
       };
     };
 
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index d4c0268ed66c5..17971b23db823 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -104,7 +104,7 @@ in
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.jre8;
-        defaultText = "pkgs.jre8";
+        defaultText = literalExpression "pkgs.jre8";
         description = ''
           The JRE package to use. Check the release notes to ensure it is supported.
         '';
@@ -113,7 +113,7 @@ in
       unifiVideoPackage = mkOption {
         type = types.package;
         default = pkgs.unifi-video;
-        defaultText = "pkgs.unifi-video";
+        defaultText = literalExpression "pkgs.unifi-video";
         description = ''
           The unifi-video package to use.
         '';
@@ -122,7 +122,7 @@ in
       mongodbPackage = mkOption {
         type = types.package;
         default = pkgs.mongodb-4_0;
-        defaultText = "pkgs.mongodb";
+        defaultText = literalExpression "pkgs.mongodb";
         description = ''
           The mongodb package to use.
         '';
diff --git a/nixos/modules/services/wayland/cage.nix b/nixos/modules/services/wayland/cage.nix
index bd97a674eb86a..273693a3b2fe6 100644
--- a/nixos/modules/services/wayland/cage.nix
+++ b/nixos/modules/services/wayland/cage.nix
@@ -18,7 +18,7 @@ in {
   options.services.cage.extraArguments = mkOption {
     type = types.listOf types.str;
     default = [];
-    defaultText = "[]";
+    defaultText = literalExpression "[]";
     description = "Additional command line arguments to pass to Cage.";
     example = ["-d"];
   };
@@ -26,6 +26,7 @@ in {
   options.services.cage.program = mkOption {
     type = types.path;
     default = "${pkgs.xterm}/bin/xterm";
+    defaultText = literalExpression ''"''${pkgs.xterm}/bin/xterm"'';
     description = ''
       Program to run in cage.
     '';
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index 59185fdbd36f2..2d809c17ff098 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -128,14 +128,14 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.atlassian-confluence;
-        defaultText = "pkgs.atlassian-confluence";
+        defaultText = literalExpression "pkgs.atlassian-confluence";
         description = "Atlassian Confluence package to use.";
       };
 
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
-        defaultText = "pkgs.oraclejre8";
+        defaultText = literalExpression "pkgs.oraclejre8";
         description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
       };
     };
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index ceab656b15e87..a8b2482d5a9cc 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -96,14 +96,14 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.atlassian-crowd;
-        defaultText = "pkgs.atlassian-crowd";
+        defaultText = literalExpression "pkgs.atlassian-crowd";
         description = "Atlassian Crowd package to use.";
       };
 
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
-        defaultText = "pkgs.oraclejre8";
+        defaultText = literalExpression "pkgs.oraclejre8";
         description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
       };
     };
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index ce04982e8a9ee..d7a26838d6f82 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -134,14 +134,14 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.atlassian-jira;
-        defaultText = "pkgs.atlassian-jira";
+        defaultText = literalExpression "pkgs.atlassian-jira";
         description = "Atlassian JIRA package to use.";
       };
 
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
-        defaultText = "pkgs.oraclejre8";
+        defaultText = literalExpression "pkgs.oraclejre8";
         description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
       };
     };
diff --git a/nixos/modules/services/web-apps/bookstack.nix b/nixos/modules/services/web-apps/bookstack.nix
index 34a31af9c9da1..54c491f8b1762 100644
--- a/nixos/modules/services/web-apps/bookstack.nix
+++ b/nixos/modules/services/web-apps/bookstack.nix
@@ -91,7 +91,7 @@ in {
       user = mkOption {
         type = types.str;
         default = user;
-        defaultText = "\${user}";
+        defaultText = literalExpression "user";
         description = "Database username.";
       };
       passwordFile = mkOption {
@@ -187,14 +187,16 @@ in {
           (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
       );
       default = {};
-      example = {
-        serverAliases = [
-          "bookstack.\${config.networking.domain}"
-        ];
-        # To enable encryption and let let's encrypt take care of certificate
-        forceSSL = true;
-        enableACME = true;
-      };
+      example = literalExpression ''
+        {
+          serverAliases = [
+            "bookstack.''${config.networking.domain}"
+          ];
+          # 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 settings.
       '';
@@ -219,7 +221,7 @@ in {
 
     assertions = [
       { assertion = db.createLocally -> db.user == user;
-        message = "services.bookstack.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true.";
+        message = "services.bookstack.database.user must be set to ${user} if services.bookstack.database.createLocally is set true.";
       }
       { assertion = db.createLocally -> db.passwordFile == null;
         message = "services.bookstack.database.passwordFile cannot be specified if services.bookstack.database.createLocally is set to true.";
diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix
new file mode 100644
index 0000000000000..474e9140ae87b
--- /dev/null
+++ b/nixos/modules/services/web-apps/code-server.nix
@@ -0,0 +1,139 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.code-server;
+  defaultUser = "code-server";
+  defaultGroup = defaultUser;
+
+in {
+  ###### interface
+  options = {
+    services.code-server = {
+      enable = mkEnableOption "code-server";
+
+      package = mkOption {
+        default = pkgs.code-server;
+        defaultText = "pkgs.code-server";
+        description = "Which code-server derivation to use.";
+        type = types.package;
+      };
+
+      extraPackages = mkOption {
+        default = [ ];
+        description = "Packages that are available in the PATH of code-server.";
+        example = "[ pkgs.go ]";
+        type = types.listOf types.package;
+      };
+
+      extraEnvironment = mkOption {
+        type = types.attrsOf types.str;
+        description =
+          "Additional environment variables to passed to code-server.";
+        default = { };
+        example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
+      };
+
+      extraArguments = mkOption {
+        default = [ "--disable-telemetry" ];
+        description = "Additional arguments that passed to code-server";
+        example = ''[ "--verbose" ]'';
+        type = types.listOf types.str;
+      };
+
+      host = mkOption {
+        default = "127.0.0.1";
+        description = "The host-ip to bind to.";
+        type = types.str;
+      };
+
+      port = mkOption {
+        default = 4444;
+        description = "The port where code-server runs.";
+        type = types.port;
+      };
+
+      auth = mkOption {
+        default = "password";
+        description = "The type of authentication to use.";
+        type = types.enum [ "none" "password" ];
+      };
+
+      hashedPassword = mkOption {
+        default = "";
+        description =
+          "Create the password with: 'echo -n 'thisismypassword' | npx argon2-cli -e'.";
+        type = types.str;
+      };
+
+      user = mkOption {
+        default = defaultUser;
+        example = "yourUser";
+        description = ''
+          The user to run code-server as.
+          By default, a user named <literal>${defaultUser}</literal> will be created.
+        '';
+        type = types.str;
+      };
+
+      group = mkOption {
+        default = defaultGroup;
+        example = "yourGroup";
+        description = ''
+          The group to run code-server under.
+          By default, a group named <literal>${defaultGroup}</literal> will be created.
+        '';
+        type = types.str;
+      };
+
+      extraGroups = mkOption {
+        default = [ ];
+        description =
+          "An array of additional groups for the <literal>${defaultUser}</literal> user.";
+        example = [ "docker" ];
+        type = types.listOf types.str;
+      };
+
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.services.code-server = {
+      description = "VSCode server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      path = cfg.extraPackages;
+      environment = {
+        HASHED_PASSWORD = cfg.hashedPassword;
+      } // cfg.extraEnvironment;
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/code-server --bind-addr ${cfg.host}:${toString cfg.port} --auth ${cfg.auth} " + builtins.concatStringsSep " " cfg.extraArguments;
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        RuntimeDirectory = cfg.user;
+        User = cfg.user;
+        Group = cfg.group;
+        Restart = "on-failure";
+      };
+
+    };
+
+    users.users."${cfg.user}" = mkMerge [
+      (mkIf (cfg.user == defaultUser) {
+        isNormalUser = true;
+        description = "code-server user";
+        inherit (cfg) group;
+      })
+      {
+        packages = cfg.extraPackages;
+        inherit (cfg) extraGroups;
+      }
+    ];
+
+    users.groups."${defaultGroup}" = mkIf (cfg.group == defaultGroup) { };
+
+  };
+
+  meta.maintainers = with maintainers; [ stackshadow ];
+}
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
index 69a89107d3102..e6772de768e0e 100644
--- a/nixos/modules/services/web-apps/cryptpad.nix
+++ b/nixos/modules/services/web-apps/cryptpad.nix
@@ -11,7 +11,7 @@ in
 
     package = mkOption {
       default = pkgs.cryptpad;
-      defaultText = "pkgs.cryptpad";
+      defaultText = literalExpression "pkgs.cryptpad";
       type = types.package;
       description = "
         Cryptpad package to use.
@@ -21,7 +21,7 @@ in
     configFile = mkOption {
       type = types.path;
       default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
-      defaultText = "\${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+      defaultText = literalExpression ''"''${package}/lib/node_modules/cryptpad/config/config.example.js"'';
       description = ''
         Path to the JavaScript configuration file.
 
diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix
new file mode 100644
index 0000000000000..f08dd65bdb0f0
--- /dev/null
+++ b/nixos/modules/services/web-apps/dex.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dex;
+  fixClient = client: if client ? secretFile then ((builtins.removeAttrs client [ "secretFile" ]) // { secret = client.secretFile; }) else client;
+  filteredSettings = mapAttrs (n: v: if n == "staticClients" then (builtins.map fixClient v) else v) cfg.settings;
+  secretFiles = flatten (builtins.map (c: if c ? secretFile then [ c.secretFile ] else []) (cfg.settings.staticClients or []));
+
+  settingsFormat = pkgs.formats.yaml {};
+  configFile = settingsFormat.generate "config.yaml" filteredSettings;
+
+  startPreScript = pkgs.writeShellScript "dex-start-pre" (''
+  '' + (concatStringsSep "\n" (builtins.map (file: ''
+    ${pkgs.replace-secret}/bin/replace-secret '${file}' '${file}' /run/dex/config.yaml
+  '') secretFiles)));
+in
+{
+  options.services.dex = {
+    enable = mkEnableOption "the OpenID Connect and OAuth2 identity provider";
+
+    settings = mkOption {
+      type = settingsFormat.type;
+      default = {};
+      example = literalExpression ''
+        {
+          # External url
+          issuer = "http://127.0.0.1:5556/dex";
+          storage = {
+            type = "postgres";
+            config.host = "/var/run/postgres";
+          };
+          web = {
+            http = "127.0.0.1:5556";
+          };
+          enablePasswordDB = true;
+          staticClients = [
+            {
+              id = "oidcclient";
+              name = "Client";
+              redirectURIs = [ "https://example.com/callback" ];
+              secretFile = "/etc/dex/oidcclient"; # The content of `secretFile` will be written into to the config as `secret`.
+            }
+          ];
+        }
+      '';
+      description = ''
+        The available options can be found in
+        <link xlink:href="https://github.com/dexidp/dex/blob/v${pkgs.dex.version}/config.yaml.dist">the example configuration</link>.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.dex = {
+      description = "dex identity provider";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ] ++ (optional (cfg.settings.storage.type == "postgres") "postgresql.service");
+
+      serviceConfig = {
+        ExecStart = "${pkgs.dex-oidc}/bin/dex serve /run/dex/config.yaml";
+        ExecStartPre = [
+          "${pkgs.coreutils}/bin/install -m 600 ${configFile} /run/dex/config.yaml"
+          "+${startPreScript}"
+        ];
+        RuntimeDirectory = "dex";
+
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/hosts"
+          "-/etc/localtime"
+          "-/etc/dex"
+        ];
+        BindPaths = optional (cfg.settings.storage.type == "postgres") "/var/run/postgresql";
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+        # ProtectClock= adds DeviceAllow=char-rtc r
+        DeviceAllow = "";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        # Port needs to be exposed to the host network
+        #PrivateNetwork = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        # Would re-mount paths ignored by temporary root
+        #ProtectSystem = "strict";
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
+        TemporaryFileSystem = "/:ro";
+        # Does not work well with the temporary root
+        #UMask = "0066";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index 050e4ee3d3296..c4fb7e2b316f8 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -33,7 +33,7 @@ in
         apply = p: p.override {
           plugins = lib.unique (p.enabledPlugins ++ cfg.plugins);
         };
-        defaultText = "pkgs.discourse";
+        defaultText = lib.literalExpression "pkgs.discourse";
         description = ''
           The discourse package to use.
         '';
@@ -45,7 +45,7 @@ in
                     config.networking.fqdn
                   else
                     config.networking.hostName;
-        defaultText = "config.networking.fqdn";
+        defaultText = lib.literalExpression "config.networking.fqdn";
         example = "discourse.example.com";
         description = ''
           The hostname to serve Discourse on.
@@ -99,7 +99,10 @@ in
       enableACME = lib.mkOption {
         type = lib.types.bool;
         default = cfg.sslCertificate == null && cfg.sslCertificateKey == null;
-        defaultText = "true, unless services.discourse.sslCertificate and services.discourse.sslCertificateKey are set.";
+        defaultText = lib.literalDocBook ''
+          <literal>true</literal>, unless <option>services.discourse.sslCertificate</option>
+          and <option>services.discourse.sslCertificateKey</option> are set.
+        '';
         description = ''
           Whether an ACME certificate should be used to secure
           connections to the server.
@@ -109,7 +112,7 @@ in
       backendSettings = lib.mkOption {
         type = with lib.types; attrsOf (nullOr (oneOf [ str int bool float ]));
         default = {};
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           {
             max_reqs_per_ip_per_minute = 300;
             max_reqs_per_ip_per_10_seconds = 60;
@@ -134,7 +137,7 @@ in
       siteSettings = lib.mkOption {
         type = json.type;
         default = {};
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           {
             required = {
               title = "My Cats";
@@ -172,6 +175,15 @@ in
       };
 
       admin = {
+        skipCreate = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = ''
+            Do not create the admin account, instead rely on other
+            existing admin accounts.
+          '';
+        };
+
         email = lib.mkOption {
           type = lib.types.str;
           example = "admin@example.com";
@@ -325,10 +337,8 @@ in
         notificationEmailAddress = lib.mkOption {
           type = lib.types.str;
           default = "${if cfg.mail.incoming.enable then "notifications" else "noreply"}@${cfg.hostname}";
-          defaultText = ''
-            "notifications@`config.services.discourse.hostname`" if
-            config.services.discourse.mail.incoming.enable is "true",
-            otherwise "noreply`config.services.discourse.hostname`"
+          defaultText = lib.literalExpression ''
+            "''${if config.services.discourse.mail.incoming.enable then "notifications" else "noreply"}@''${config.services.discourse.hostname}"
           '';
           description = ''
             The <literal>from:</literal> email address used when
@@ -439,7 +449,7 @@ in
           replyEmailAddress = lib.mkOption {
             type = lib.types.str;
             default = "%{reply_key}@${cfg.hostname}";
-            defaultText = "%{reply_key}@`config.services.discourse.hostname`";
+            defaultText = lib.literalExpression ''"%{reply_key}@''${config.services.discourse.hostname}"'';
             description = ''
               Template for reply by email incoming email address, for
               example: %{reply_key}@reply.example.com or
@@ -450,7 +460,7 @@ in
           mailReceiverPackage = lib.mkOption {
             type = lib.types.package;
             default = pkgs.discourse-mail-receiver;
-            defaultText = "pkgs.discourse-mail-receiver";
+            defaultText = lib.literalExpression "pkgs.discourse-mail-receiver";
             description = ''
               The discourse-mail-receiver package to use.
             '';
@@ -475,7 +485,7 @@ in
       plugins = lib.mkOption {
         type = lib.types.listOf lib.types.package;
         default = [];
-        example = lib.literalExample ''
+        example = lib.literalExpression ''
           with config.services.discourse.package.plugins; [
             discourse-canned-replies
             discourse-github
@@ -721,12 +731,24 @@ in
             lib.optionalString (file != null) ''
               replace-secret '${file}' '${file}' /run/discourse/config/discourse.conf
             '';
+
+          mkAdmin = ''
+            export ADMIN_EMAIL="${cfg.admin.email}"
+            export ADMIN_NAME="${cfg.admin.fullName}"
+            export ADMIN_USERNAME="${cfg.admin.username}"
+            ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})"
+            export ADMIN_PASSWORD
+            discourse-rake admin:create_noninteractively
+          '';
+
         in ''
           set -o errexit -o pipefail -o nounset -o errtrace
           shopt -s inherit_errexit
 
           umask u=rwx,g=rx,o=
 
+          rm -rf /var/lib/discourse/tmp/*
+
           cp -r ${cfg.package}/share/discourse/config.dist/* /run/discourse/config/
           cp -r ${cfg.package}/share/discourse/public.dist/* /run/discourse/public/
           ln -sf /var/lib/discourse/uploads /run/discourse/public/uploads
@@ -748,14 +770,9 @@ in
           )
 
           discourse-rake db:migrate >>/var/log/discourse/db_migration.log
-          chmod -R u+w /run/discourse/tmp/
+          chmod -R u+w /var/lib/discourse/tmp/
 
-          export ADMIN_EMAIL="${cfg.admin.email}"
-          export ADMIN_NAME="${cfg.admin.fullName}"
-          export ADMIN_USERNAME="${cfg.admin.username}"
-          ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})"
-          export ADMIN_PASSWORD
-          discourse-rake admin:create_noninteractively
+          ${lib.optionalString (!cfg.admin.skipCreate) mkAdmin}
 
           discourse-rake themes:update
           discourse-rake uploads:regenerate_missing_optimized
@@ -768,7 +785,6 @@ in
         RuntimeDirectory = map (p: "discourse/" + p) [
           "config"
           "home"
-          "tmp"
           "assets/javascripts/plugins"
           "public"
           "sockets"
@@ -777,6 +793,7 @@ in
         StateDirectory = map (p: "discourse/" + p) [
           "uploads"
           "backups"
+          "tmp"
         ];
         StateDirectoryMode = 0750;
         LogsDirectory = "discourse";
diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
index a5f48e744fdc3..7f2ed82ee33e4 100644
--- a/nixos/modules/services/web-apps/documize.nix
+++ b/nixos/modules/services/web-apps/documize.nix
@@ -26,6 +26,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.documize-community;
+      defaultText = literalExpression "pkgs.documize-community";
       description = ''
         Which package to use for documize.
       '';
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 685cb4967030c..fc0e23729b3c4 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -1,16 +1,21 @@
-{ config, lib, pkgs, ... }:
+{ config, pkgs, lib, ... }:
 
 let
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types maintainers recursiveUpdate;
+  inherit (lib) any attrValues concatMapStrings concatMapStringsSep flatten literalExpression;
+  inherit (lib) filterAttrs mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
 
-  inherit (lib) mkEnableOption mkForce mkIf mkMerge mkOption optionalAttrs recursiveUpdate types maintainers;
-  inherit (lib) concatMapStringsSep flatten mapAttrs mapAttrs' mapAttrsToList nameValuePair concatMapStringSep;
-
-  eachSite = config.services.dokuwiki;
-
+  cfg = migrateOldAttrs config.services.dokuwiki;
+  eachSite = cfg.sites;
   user = "dokuwiki";
-  group = config.services.nginx.group;
+  webserver = config.services.${cfg.webserver};
+  stateDir = hostName: "/var/lib/dokuwiki/${hostName}/data";
+
+  # Migrate config.services.dokuwiki.<hostName> to config.services.dokuwiki.sites.<hostName>
+  oldSites = filterAttrs (o: _: o != "sites" && o != "webserver");
+  migrateOldAttrs = cfg: cfg // { sites = cfg.sites // oldSites cfg; };
 
-  dokuwikiAclAuthConfig = cfg: pkgs.writeText "acl.auth.php" ''
+  dokuwikiAclAuthConfig = hostName: cfg: pkgs.writeText "acl.auth-${hostName}.php" ''
     # acl.auth.php
     # <?php exit()?>
     #
@@ -19,7 +24,7 @@ let
     ${toString cfg.acl}
   '';
 
-  dokuwikiLocalConfig = cfg: pkgs.writeText "local.php" ''
+  dokuwikiLocalConfig = hostName: cfg: pkgs.writeText "local-${hostName}.php" ''
     <?php
     $conf['savedir'] = '${cfg.stateDir}';
     $conf['superuser'] = '${toString cfg.superUser}';
@@ -28,11 +33,12 @@ let
     ${toString cfg.extraConfig}
   '';
 
-  dokuwikiPluginsLocalConfig = cfg: pkgs.writeText "plugins.local.php" ''
+  dokuwikiPluginsLocalConfig = hostName: cfg: pkgs.writeText "plugins.local-${hostName}.php" ''
     <?php
     ${cfg.pluginsConfig}
   '';
 
+
   pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
     pname = "dokuwiki-${hostName}";
     version = src.version;
@@ -43,13 +49,13 @@ let
       cp -r * $out/
 
       # symlink the dokuwiki config
-      ln -s ${dokuwikiLocalConfig cfg} $out/share/dokuwiki/local.php
+      ln -s ${dokuwikiLocalConfig hostName cfg} $out/share/dokuwiki/local.php
 
       # symlink plugins config
-      ln -s ${dokuwikiPluginsLocalConfig cfg} $out/share/dokuwiki/plugins.local.php
+      ln -s ${dokuwikiPluginsLocalConfig hostName cfg} $out/share/dokuwiki/plugins.local.php
 
       # symlink acl
-      ln -s ${dokuwikiAclAuthConfig cfg} $out/share/dokuwiki/acl.auth.php
+      ln -s ${dokuwikiAclAuthConfig hostName cfg} $out/share/dokuwiki/acl.auth.php
 
       # symlink additional plugin(s) and templates(s)
       ${concatMapStringsSep "\n" (template: "ln -s ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates}
@@ -57,332 +63,388 @@ let
     '';
   };
 
-  siteOpts = { config, lib, name, ...}: {
-    options = {
-      enable = mkEnableOption "DokuWiki web application.";
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.dokuwiki;
-        description = "Which dokuwiki package to use.";
-      };
-
-      hostName = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = "FQDN for the instance.";
-      };
-
-      stateDir = mkOption {
-        type = types.path;
-        default = "/var/lib/dokuwiki/${name}/data";
-        description = "Location of the dokuwiki state directory.";
-      };
+  siteOpts = { config, lib, name, ... }:
+    {
+      options = {
+        enable = mkEnableOption "DokuWiki web application.";
+
+        package = mkOption {
+          type = types.package;
+          default = pkgs.dokuwiki;
+          defaultText = literalExpression "pkgs.dokuwiki";
+          description = "Which DokuWiki package to use.";
+        };
 
-      acl = mkOption {
-        type = types.nullOr types.lines;
-        default = null;
-        example = "*               @ALL               8";
-        description = ''
-          Access Control Lists: see <link xlink:href="https://www.dokuwiki.org/acl"/>
-          Mutually exclusive with services.dokuwiki.aclFile
-          Set this to a value other than null to take precedence over aclFile option.
-
-          Warning: Consider using aclFile instead if you do not
-          want to store the ACL in the world-readable Nix store.
-        '';
-      };
+        stateDir = mkOption {
+          type = types.path;
+          default = "/var/lib/dokuwiki/${name}/data";
+          description = "Location of the DokuWiki state directory.";
+        };
 
-      aclFile = mkOption {
-        type = with types; nullOr str;
-        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.
-          Consult documentation <link xlink:href="https://www.dokuwiki.org/acl"/> for further instructions.
-          Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist"/>
-        '';
-        example = "/var/lib/dokuwiki/${name}/acl.auth.php";
-      };
+        acl = mkOption {
+          type = types.nullOr types.lines;
+          default = null;
+          example = "*               @ALL               8";
+          description = ''
+            Access Control Lists: see <link xlink:href="https://www.dokuwiki.org/acl"/>
+            Mutually exclusive with services.dokuwiki.aclFile
+            Set this to a value other than null to take precedence over aclFile option.
+
+            Warning: Consider using aclFile instead if you do not
+            want to store the ACL in the world-readable Nix store.
+          '';
+        };
 
-      aclUse = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Necessary for users to log in into the system.
-          Also limits anonymous users. When disabled,
-          everyone is able to create and edit content.
-        '';
-      };
+        aclFile = mkOption {
+          type = with types; nullOr str;
+          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.
+            Consult documentation <link xlink:href="https://www.dokuwiki.org/acl"/> for further instructions.
+            Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist"/>
+          '';
+          example = "/var/lib/dokuwiki/${name}/acl.auth.php";
+        };
 
-      pluginsConfig = mkOption {
-        type = types.lines;
-        default = ''
-          $plugins['authad'] = 0;
-          $plugins['authldap'] = 0;
-          $plugins['authmysql'] = 0;
-          $plugins['authpgsql'] = 0;
-        '';
-        description = ''
-          List of the dokuwiki (un)loaded plugins.
-        '';
-      };
+        aclUse = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Necessary for users to log in into the system.
+            Also limits anonymous users. When disabled,
+            everyone is able to create and edit content.
+          '';
+        };
 
-      superUser = mkOption {
-        type = types.nullOr types.str;
-        default = "@admin";
-        description = ''
-          You can set either a username, a list of usernames (“admin1,admin2”),
-          or the name of a group by prepending an @ char to the groupname
-          Consult documentation <link xlink:href="https://www.dokuwiki.org/config:superuser"/> for further instructions.
-        '';
-      };
+        pluginsConfig = mkOption {
+          type = types.lines;
+          default = ''
+            $plugins['authad'] = 0;
+            $plugins['authldap'] = 0;
+            $plugins['authmysql'] = 0;
+            $plugins['authpgsql'] = 0;
+          '';
+          description = ''
+            List of the dokuwiki (un)loaded plugins.
+          '';
+        };
 
-      usersFile = mkOption {
-        type = with types; nullOr str;
-        default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
-        description = ''
-          Location of the dokuwiki users file. List of users. Format:
-          login:passwordhash:Real Name:email:groups,comma,separated
-          Create passwordHash easily by using:$ mkpasswd -5 password `pwgen 8 1`
-          Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist"/>
+        superUser = mkOption {
+          type = types.nullOr types.str;
+          default = "@admin";
+          description = ''
+            You can set either a username, a list of usernames (“admin1,admin2”),
+            or the name of a group by prepending an @ char to the groupname
+            Consult documentation <link xlink:href="https://www.dokuwiki.org/config:superuser"/> for further instructions.
           '';
-        example = "/var/lib/dokuwiki/${name}/users.auth.php";
-      };
+        };
 
-      disableActions = mkOption {
-        type = types.nullOr types.str;
-        default = "";
-        example = "search,register";
-        description = ''
-          Disable individual action modes. Refer to
-          <link xlink:href="https://www.dokuwiki.org/config:action_modes"/>
-          for details on supported values.
-        '';
-      };
+        usersFile = mkOption {
+          type = with types; nullOr str;
+          default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
+          description = ''
+            Location of the dokuwiki users file. List of users. Format:
+            login:passwordhash:Real Name:email:groups,comma,separated
+            Create passwordHash easily by using:$ mkpasswd -5 password `pwgen 8 1`
+            Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist"/>
+            '';
+          example = "/var/lib/dokuwiki/${name}/users.auth.php";
+        };
 
-      extraConfig = mkOption {
-        type = types.nullOr types.lines;
-        default = null;
-        example = ''
-          $conf['title'] = 'My Wiki';
-          $conf['userewrite'] = 1;
-        '';
-        description = ''
-          DokuWiki configuration. Refer to
-          <link xlink:href="https://www.dokuwiki.org/config"/>
-          for details on supported values.
-        '';
-      };
+        disableActions = mkOption {
+          type = types.nullOr types.str;
+          default = "";
+          example = "search,register";
+          description = ''
+            Disable individual action modes. Refer to
+            <link xlink:href="https://www.dokuwiki.org/config:action_modes"/>
+            for details on supported values.
+          '';
+        };
 
-      plugins = mkOption {
-        type = types.listOf types.path;
-        default = [];
-        description = ''
-              List of path(s) to respective plugin(s) which are copied from the 'plugin' directory.
-              <note><para>These plugins need to be packaged before use, see example.</para></note>
-        '';
-        example = ''
-              # Let's package the icalevents plugin
-              plugin-icalevents = pkgs.stdenv.mkDerivation {
-                name = "icalevents";
-                # Download the plugin from the dokuwiki site
-                src = pkgs.fetchurl {
-                  url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip";
-                  sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
-                };
-                sourceRoot = ".";
-                # We need unzip to build this package
-                nativeBuildInputs = [ pkgs.unzip ];
-                # Installing simply means copying all files to the output directory
-                installPhase = "mkdir -p $out; cp -R * $out/";
-              };
-
-              # And then pass this theme to the plugin list like this:
-              plugins = [ plugin-icalevents ];
-        '';
-      };
+        plugins = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = ''
+                List of path(s) to respective plugin(s) which are copied from the 'plugin' directory.
+                <note><para>These plugins need to be packaged before use, see example.</para></note>
+          '';
+          example = literalExpression ''
+                let
+                  # Let's package the icalevents plugin
+                  plugin-icalevents = pkgs.stdenv.mkDerivation {
+                    name = "icalevents";
+                    # Download the plugin from the dokuwiki site
+                    src = pkgs.fetchurl {
+                      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip";
+                      sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
+                    };
+                    sourceRoot = ".";
+                    # We need unzip to build this package
+                    buildInputs = [ pkgs.unzip ];
+                    # Installing simply means copying all files to the output directory
+                    installPhase = "mkdir -p $out; cp -R * $out/";
+                  };
+                # And then pass this theme to the plugin list like this:
+                in [ plugin-icalevents ]
+          '';
+        };
 
-      templates = mkOption {
-        type = types.listOf types.path;
-        default = [];
-        description = ''
-              List of path(s) to respective template(s) which are copied from the 'tpl' directory.
-              <note><para>These templates need to be packaged before use, see example.</para></note>
-        '';
-        example = ''
-              # Let's package the bootstrap3 theme
-              template-bootstrap3 = pkgs.stdenv.mkDerivation {
-                name = "bootstrap3";
-                # Download the theme from the dokuwiki site
-                src = pkgs.fetchurl {
-                  url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip";
-                  sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
-                };
-                # We need unzip to build this package
-                nativeBuildInputs = [ pkgs.unzip ];
-                # Installing simply means copying all files to the output directory
-                installPhase = "mkdir -p $out; cp -R * $out/";
-              };
-
-              # And then pass this theme to the template list like this:
-              templates = [ template-bootstrap3 ];
-        '';
-      };
+        templates = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = ''
+                List of path(s) to respective template(s) which are copied from the 'tpl' directory.
+                <note><para>These templates need to be packaged before use, see example.</para></note>
+          '';
+          example = literalExpression ''
+                let
+                  # Let's package the bootstrap3 theme
+                  template-bootstrap3 = pkgs.stdenv.mkDerivation {
+                    name = "bootstrap3";
+                    # Download the theme from the dokuwiki site
+                    src = pkgs.fetchurl {
+                      url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip";
+                      sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
+                    };
+                    # We need unzip to build this package
+                    buildInputs = [ pkgs.unzip ];
+                    # Installing simply means copying all files to the output directory
+                    installPhase = "mkdir -p $out; cp -R * $out/";
+                  };
+                # And then pass this theme to the template list like this:
+                in [ template-bootstrap3 ]
+          '';
+        };
 
-      poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ str int bool ]);
-        default = {
-          "pm" = "dynamic";
-          "pm.max_children" = 32;
-          "pm.start_servers" = 2;
-          "pm.min_spare_servers" = 2;
-          "pm.max_spare_servers" = 4;
-          "pm.max_requests" = 500;
+        poolConfig = mkOption {
+          type = with types; attrsOf (oneOf [ str int bool ]);
+          default = {
+            "pm" = "dynamic";
+            "pm.max_children" = 32;
+            "pm.start_servers" = 2;
+            "pm.min_spare_servers" = 2;
+            "pm.max_spare_servers" = 4;
+            "pm.max_requests" = 500;
+          };
+          description = ''
+            Options for the DokuWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+            for details on configuration directives.
+          '';
         };
-        description = ''
-          Options for the dokuwiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
-          for details on configuration directives.
-        '';
-      };
 
-      nginx = mkOption {
-        type = types.submodule (
-          recursiveUpdate
-            (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
-        );
-        default = {};
-        example = {
-          serverAliases = [
-            "wiki.\${config.networking.domain}"
-          ];
-          # To enable encryption and let let's encrypt take care of certificate
-          forceSSL = true;
-          enableACME = true;
+        extraConfig = mkOption {
+          type = types.nullOr types.lines;
+          default = null;
+          example = ''
+            $conf['title'] = 'My Wiki';
+            $conf['userewrite'] = 1;
+          '';
+          description = ''
+            DokuWiki configuration. Refer to
+            <link xlink:href="https://www.dokuwiki.org/config"/>
+            for details on supported values.
+          '';
         };
-        description = ''
-          With this option, you can customize the nginx virtualHost settings.
-        '';
+
       };
+
     };
-  };
 in
 {
   # interface
   options = {
     services.dokuwiki = mkOption {
-      type = types.attrsOf (types.submodule siteOpts);
+      type = types.submodule {
+        # Used to support old interface
+        freeformType = types.attrsOf (types.submodule siteOpts);
+
+        # New interface
+        options.sites = mkOption {
+          type = types.attrsOf (types.submodule siteOpts);
+          default = {};
+          description = "Specification of one or more DokuWiki sites to serve";
+        };
+
+        options.webserver = mkOption {
+          type = types.enum [ "nginx" "caddy" ];
+          default = "nginx";
+          description = ''
+            Whether to use nginx or caddy for 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.
+
+            Further apache2 configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
+            See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+          '';
+        };
+      };
       default = {};
-      description = "Sepcification of one or more dokuwiki sites to serve.";
+      description = "DokuWiki configuration";
     };
+
   };
 
   # implementation
-
-  config = mkIf (eachSite != {}) {
-
-    warnings = mapAttrsToList (hostName: cfg: mkIf (cfg.superUser == null) "Not setting services.dokuwiki.${hostName} superUser will impair your ability to administer DokuWiki") eachSite;
+  config = mkIf (eachSite != {}) (mkMerge [{
 
     assertions = flatten (mapAttrsToList (hostName: cfg:
     [{
       assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null);
-      message = "Either services.dokuwiki.${hostName}.acl or services.dokuwiki.${hostName}.aclFile is mandatory if aclUse true";
+      message = "Either services.dokuwiki.sites.${hostName}.acl or services.dokuwiki.sites.${hostName}.aclFile is mandatory if aclUse true";
     }
     {
       assertion = cfg.usersFile != null -> cfg.aclUse != false;
-      message = "services.dokuwiki.${hostName}.aclUse must must be true if usersFile is not null";
+      message = "services.dokuwiki.sites.${hostName}.aclUse must must be true if usersFile is not null";
     }
     ]) eachSite);
 
+    warnings = mapAttrsToList (hostName: _: ''services.dokuwiki."${hostName}" is deprecated use services.dokuwiki.sites."${hostName}"'') (oldSites cfg);
+
     services.phpfpm.pools = mapAttrs' (hostName: cfg: (
       nameValuePair "dokuwiki-${hostName}" {
         inherit user;
-        inherit group;
+        group = webserver.group;
+
         phpEnv = {
-          DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig cfg}";
-          DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig cfg}";
+          DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}";
+          DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}";
         } // optionalAttrs (cfg.usersFile != null) {
           DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
         } //optionalAttrs (cfg.aclUse) {
-          DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig cfg}" else "${toString cfg.aclFile}";
+          DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig hostName cfg}" else "${toString cfg.aclFile}";
         };
 
         settings = {
-          "listen.mode" = "0660";
-          "listen.owner" = user;
-          "listen.group" = group;
+          "listen.owner" = webserver.user;
+          "listen.group" = webserver.group;
         } // cfg.poolConfig;
-      })) eachSite;
-
-    services.nginx = {
-      enable = true;
-      virtualHosts = mapAttrs (hostName: cfg:  mkMerge [ cfg.nginx {
-        root = mkForce "${pkg hostName cfg}/share/dokuwiki";
-        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;";
-        };
+      }
+    )) eachSite;
 
-        locations."~ ^/data/" = {
-          root = "${cfg.stateDir}";
-          extraConfig = "internal;";
-        };
+  }
 
-        locations."~ ^/lib.*\\.(js|css|gif|png|ico|jpg|jpeg)$" = {
-          extraConfig = "expires 365d;";
-        };
+  {
+    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
+      "d ${stateDir hostName}/attic 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/cache 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/index 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/locks 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/media 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/media_attic 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/media_meta 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/meta 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/pages 0750 ${user} ${webserver.group} - -"
+      "d ${stateDir hostName}/tmp 0750 ${user} ${webserver.group} - -"
+    ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist"
+    ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${webserver.group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist"
+    ) eachSite);
 
-        locations."/" = {
-          priority = 1;
-          index = "doku.php";
-          extraConfig = "try_files $uri $uri/ @dokuwiki;";
-        };
+    users.users.${user} = {
+      group = webserver.group;
+      isSystemUser = true;
+    };
+  }
 
-        locations."@dokuwiki" = {
-          extraConfig = ''
+  (mkIf (cfg.webserver == "nginx") {
+    services.nginx = {
+      enable = true;
+      virtualHosts = mapAttrs (hostName: cfg: {
+        serverName = mkDefault hostName;
+        root = "${pkg hostName cfg}/share/dokuwiki";
+
+        locations = {
+          "~ /(conf/|bin/|inc/|install.php)" = {
+            extraConfig = "deny all;";
+          };
+
+          "~ ^/data/" = {
+            root = "${stateDir hostName}";
+            extraConfig = "internal;";
+          };
+
+          "~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = {
+            extraConfig = "expires 365d;";
+          };
+
+          "/" = {
+            priority = 1;
+            index = "doku.php";
+            extraConfig = ''try_files $uri $uri/ @dokuwiki;'';
+          };
+
+          "@dokuwiki" = {
+            extraConfig = ''
               # rewrites "doku.php/" out of the URLs if you set the userwrite setting to .htaccess in dokuwiki config page
               rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
               rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
               rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
               rewrite ^/(.*) /doku.php?id=$1&$args last;
-          '';
-        };
+            '';
+          };
 
-        locations."~ \\.php$" = {
-          extraConfig = ''
+          "~ \\.php$" = {
+            extraConfig = ''
               try_files $uri $uri/ /doku.php;
               include ${pkgs.nginx}/conf/fastcgi_params;
               fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
               fastcgi_param REDIRECT_STATUS 200;
               fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket};
-              ${lib.optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"}
-          '';
+              '';
+          };
+
         };
-      }]) eachSite;
+      }) eachSite;
     };
+  })
 
-    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
-      "d ${cfg.stateDir}/attic 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/cache 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/index 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/locks 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/media 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/media_attic 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/media_meta 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/meta 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/pages 0750 ${user} ${group} - -"
-      "d ${cfg.stateDir}/tmp 0750 ${user} ${group} - -"
-    ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist"
-    ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist"
-    ) eachSite);
+  (mkIf (cfg.webserver == "caddy") {
+    services.caddy = {
+      enable = true;
+      virtualHosts = mapAttrs' (hostName: cfg: (
+        nameValuePair "http://${hostName}" {
+          extraConfig = ''
+            root * ${pkg hostName cfg}/share/dokuwiki
+            file_server
 
-    users.users.${user} = {
-      group = group;
-      isSystemUser = true;
+            encode zstd gzip
+            php_fastcgi unix/${config.services.phpfpm.pools."dokuwiki-${hostName}".socket}
+
+            @restrict_files {
+              path /data/* /conf/* /bin/* /inc/* /vendor/* /install.php
+            }
+
+            respond @restrict_files 404
+
+            @allow_media {
+              path_regexp path ^/_media/(.*)$
+            }
+            rewrite @allow_media /lib/exe/fetch.php?media=/{http.regexp.path.1}
+
+            @allow_detail   {
+              path /_detail*
+            }
+            rewrite @allow_detail /lib/exe/detail.php?media={path}
+
+            @allow_export   {
+              path /_export*
+              path_regexp export /([^/]+)/(.*)
+            }
+            rewrite @allow_export /doku.php?do=export_{http.regexp.export.1}&id={http.regexp.export.2}
+
+            try_files {path} {path}/ /doku.php?id={path}&{query}
+          '';
+        }
+      )) eachSite;
     };
-  };
+  })
 
-  meta.maintainers = with maintainers; [ _1000101 ];
+  ]);
 
+  meta.maintainers = with maintainers; [
+    _1000101
+    onny
+  ];
 }
diff --git a/nixos/modules/services/web-apps/engelsystem.nix b/nixos/modules/services/web-apps/engelsystem.nix
index b87fecae65f26..06c3c6dfc3d7d 100644
--- a/nixos/modules/services/web-apps/engelsystem.nix
+++ b/nixos/modules/services/web-apps/engelsystem.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, utils, ... }:
 
 let
-  inherit (lib) mkDefault mkEnableOption mkIf mkOption types literalExample;
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption types literalExpression;
   cfg = config.services.engelsystem;
 in {
   options = {
@@ -24,9 +24,9 @@ in {
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.engelsystem";
         description = "Engelsystem package used for the service.";
         default = pkgs.engelsystem;
+        defaultText = literalExpression "pkgs.engelsystem";
       };
 
       createDatabase = mkOption {
diff --git a/nixos/modules/services/web-apps/fluidd.nix b/nixos/modules/services/web-apps/fluidd.nix
index c632b8ff7199a..6ac1acc9d036e 100644
--- a/nixos/modules/services/web-apps/fluidd.nix
+++ b/nixos/modules/services/web-apps/fluidd.nix
@@ -12,7 +12,7 @@ in
       type = types.package;
       description = "Fluidd package to be used in the module";
       default = pkgs.fluidd;
-      defaultText = "pkgs.fluidd";
+      defaultText = literalExpression "pkgs.fluidd";
     };
 
     hostName = mkOption {
@@ -25,9 +25,11 @@ in
       type = types.submodule
         (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
       default = { };
-      example = {
-        serverAliases = [ "fluidd.\${config.networking.domain}" ];
-      };
+      example = literalExpression ''
+        {
+          serverAliases = [ "fluidd.''${config.networking.domain}" ];
+        }
+      '';
       description = "Extra configuration for the nginx virtual host of fluidd.";
     };
   };
diff --git a/nixos/modules/services/web-apps/galene.nix b/nixos/modules/services/web-apps/galene.nix
index dd63857a55c8c..db9dfeb474995 100644
--- a/nixos/modules/services/web-apps/galene.nix
+++ b/nixos/modules/services/web-apps/galene.nix
@@ -80,6 +80,7 @@ in
       staticDir = mkOption {
         type = types.str;
         default = "${cfg.package.static}/static";
+        defaultText = literalExpression ''"''${package.static}/static"'';
         example = "/var/lib/galene/static";
         description = "Web server directory.";
       };
@@ -107,7 +108,7 @@ in
 
       package = mkOption {
         default = pkgs.galene;
-        defaultText = "pkgs.galene";
+        defaultText = literalExpression "pkgs.galene";
         type = types.package;
         description = ''
           Package for running Galene.
diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix
index 864587aea5651..9ee9dbf1aa495 100644
--- a/nixos/modules/services/web-apps/gerrit.nix
+++ b/nixos/modules/services/web-apps/gerrit.nix
@@ -64,13 +64,14 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.gerrit;
+        defaultText = literalExpression "pkgs.gerrit";
         description = "Gerrit package to use";
       };
 
       jvmPackage = mkOption {
         type = types.package;
         default = pkgs.jre_headless;
-        defaultText = "pkgs.jre_headless";
+        defaultText = literalExpression "pkgs.jre_headless";
         description = "Java Runtime Environment package to use";
       };
 
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
index d940f3d3daecd..e0c00fe67ea32 100644
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixos/modules/services/web-apps/hedgedoc.nix
@@ -73,7 +73,7 @@ in
       port = mkOption {
         type = types.int;
         default = 3000;
-        example = "80";
+        example = 80;
         description = ''
           Port to listen on.
         '';
@@ -135,7 +135,7 @@ in
       csp = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        example = literalExample ''
+        example = literalExpression ''
           {
             enable = true;
             directives = {
@@ -222,7 +222,7 @@ in
       db = mkOption {
         type = types.attrs;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           {
             dialect = "sqlite";
             storage = "/var/lib/${name}/db.${name}.sqlite";
@@ -313,7 +313,7 @@ in
       errorPath = mkOption {
         type = types.nullOr types.str;
         default = null;
-        defaultText = "./public/views/error.ejs";
+        defaultText = literalExpression "./public/views/error.ejs";
         description = ''
           Path to the error template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -322,7 +322,7 @@ in
       prettyPath = mkOption {
         type = types.nullOr types.str;
         default = null;
-        defaultText = "./public/views/pretty.ejs";
+        defaultText = literalExpression "./public/views/pretty.ejs";
         description = ''
           Path to the pretty template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -331,7 +331,7 @@ in
       slidePath = mkOption {
         type = types.nullOr types.str;
         default = null;
-        defaultText = "./public/views/slide.hbs";
+        defaultText = literalExpression "./public/views/slide.hbs";
         description = ''
           Path to the slide template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
@@ -340,7 +340,7 @@ in
       uploadsPath = mkOption {
         type = types.str;
         default = "${cfg.workDir}/uploads";
-        defaultText = "/var/lib/${name}/uploads";
+        defaultText = literalExpression "/var/lib/${name}/uploads";
         description = ''
           Path under which uploaded files are saved.
         '';
@@ -539,6 +539,69 @@ in
                 Specify the OAuth token URL.
               '';
             };
+            baseURL = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the OAuth base URL.
+              '';
+            };
+            userProfileURL = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the OAuth userprofile URL.
+              '';
+            };
+            userProfileUsernameAttr = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the name of the attribute for the username from the claim.
+              '';
+            };
+            userProfileDisplayNameAttr = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the name of the attribute for the display name from the claim.
+              '';
+            };
+            userProfileEmailAttr = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the name of the attribute for the email from the claim.
+              '';
+            };
+            scope = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the OAuth scope.
+              '';
+            };
+            providerName = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the name to be displayed for this strategy.
+              '';
+            };
+            rolesClaim = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify the role claim name.
+              '';
+            };
+            accessRole = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Specify role which should be included in the ID token roles claim to grant access
+              '';
+            };
             clientID = mkOption {
               type = types.str;
               description = ''
@@ -925,6 +988,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.hedgedoc;
+      defaultText = literalExpression "pkgs.hedgedoc";
       description = ''
         Package that provides HedgeDoc.
       '';
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
index a69767194c336..4f6a34e6d2fe5 100644
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixos/modules/services/web-apps/hledger-web.nix
@@ -20,7 +20,7 @@ in {
     port = mkOption {
       type = types.port;
       default = 5000;
-      example = "80";
+      example = 80;
       description = ''
         Port to listen on.
       '';
@@ -118,7 +118,7 @@ in {
         ++ extraOptions);
     in {
       description = "hledger-web - web-app for the hledger accounting tool.";
-      documentation = [ https://hledger.org/hledger-web.html ];
+      documentation = [ "https://hledger.org/hledger-web.html" ];
       wantedBy = [ "multi-user.target" ];
       after = [ "networking.target" ];
       serviceConfig = mkMerge [
diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
index f8f0854f1bcb5..b9761061aaaeb 100644
--- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -59,7 +59,7 @@ in {
     modulePackages = mkOption {
       type = attrsOf package;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "snow" = icingaweb2Modules.theme-snow;
         }
diff --git a/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixos/modules/services/web-apps/ihatemoney/default.nix
index b4987fa4702cf..238241854c1ce 100644
--- a/nixos/modules/services/web-apps/ihatemoney/default.nix
+++ b/nixos/modules/services/web-apps/ihatemoney/default.nix
@@ -33,11 +33,14 @@ let
           then "sqlite:////var/lib/ihatemoney/ihatemoney.sqlite"
           else "postgresql:///${db}"}'
         SQLALCHEMY_TRACK_MODIFICATIONS = False
-        MAIL_DEFAULT_SENDER = ("${cfg.defaultSender.name}", "${cfg.defaultSender.email}")
+        MAIL_DEFAULT_SENDER = (r"${cfg.defaultSender.name}", r"${cfg.defaultSender.email}")
         ACTIVATE_DEMO_PROJECT = ${toBool cfg.enableDemoProject}
-        ADMIN_PASSWORD = "${toString cfg.adminHashedPassword /*toString null == ""*/}"
+        ADMIN_PASSWORD = r"${toString cfg.adminHashedPassword /*toString null == ""*/}"
         ALLOW_PUBLIC_PROJECT_CREATION = ${toBool cfg.enablePublicProjectCreation}
         ACTIVATE_ADMIN_DASHBOARD = ${toBool cfg.enableAdminDashboard}
+        SESSION_COOKIE_SECURE = ${toBool cfg.secureCookie}
+        ENABLE_CAPTCHA = ${toBool cfg.enableCaptcha}
+        LEGAL_LINK = r"${toString cfg.legalLink}"
 
         ${cfg.extraConfig}
   '';
@@ -79,9 +82,20 @@ in
           description = "The email of the sender of ihatemoney emails";
         };
       };
+      secureCookie = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Use secure cookies. Disable this when ihatemoney is served via http instead of https";
+      };
       enableDemoProject = mkEnableOption "access to the demo project in ihatemoney";
       enablePublicProjectCreation = mkEnableOption "permission to create projects in ihatemoney by anyone";
       enableAdminDashboard = mkEnableOption "ihatemoney admin dashboard";
+      enableCaptcha = mkEnableOption "a simplistic captcha for some forms";
+      legalLink = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "The URL to a page explaining legal statements about your service, eg. GDPR-related information.";
+      };
       extraConfig = mkOption {
         type = types.str;
         default = "";
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
new file mode 100644
index 0000000000000..7fb826af5835e
--- /dev/null
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -0,0 +1,263 @@
+{ lib, config, pkgs, options, ... }:
+let
+  cfg = config.services.invidious;
+  # To allow injecting secrets with jq, json (instead of yaml) is used
+  settingsFormat = pkgs.formats.json { };
+  inherit (lib) types;
+
+  settingsFile = settingsFormat.generate "invidious-settings" cfg.settings;
+
+  serviceConfig = {
+    systemd.services.invidious = {
+      description = "Invidious (An alternative YouTube front-end)";
+      wants = [ "network-online.target" ];
+      after = [ "syslog.target" "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      script =
+        let
+          jqFilter = "."
+            + lib.optionalString (cfg.database.host != null) "[0].db.password = \"'\"'\"$(cat ${lib.escapeShellArg cfg.database.passwordFile})\"'\"'\""
+            + " | .[0]"
+            + lib.optionalString (cfg.extraSettingsFile != null) " * .[1]";
+          jqFiles = [ settingsFile ] ++ lib.optional (cfg.extraSettingsFile != null) cfg.extraSettingsFile;
+        in
+        ''
+          export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s "${jqFilter}" ${lib.escapeShellArgs jqFiles})"
+          exec ${cfg.package}/bin/invidious
+        '';
+
+      serviceConfig = {
+        RestartSec = "2s";
+        DynamicUser = true;
+
+        CapabilityBoundingSet = "";
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+      };
+    };
+
+    services.invidious.settings = {
+      inherit (cfg) port;
+
+      # Automatically initialises and migrates the database if necessary
+      check_tables = true;
+
+      db = {
+        user = lib.mkDefault "kemal";
+        dbname = lib.mkDefault "invidious";
+        port = cfg.database.port;
+        # Blank for unix sockets, see
+        # https://github.com/will/crystal-pg/blob/1548bb255210/src/pq/conninfo.cr#L100-L108
+        host = if cfg.database.host == null then "" else cfg.database.host;
+        # Not needed because peer authentication is enabled
+        password = lib.mkIf (cfg.database.host == null) "";
+      };
+    } // (lib.optionalAttrs (cfg.domain != null) {
+      inherit (cfg) domain;
+    });
+
+    assertions = [{
+      assertion = cfg.database.host != null -> cfg.database.passwordFile != null;
+      message = "If database host isn't null, database password needs to be set";
+    }];
+  };
+
+  # Settings necessary for running with an automatically managed local database
+  localDatabaseConfig = lib.mkIf cfg.database.createLocally {
+    # Default to using the local database if we create it
+    services.invidious.database.host = lib.mkDefault null;
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = lib.singleton cfg.settings.db.dbname;
+      ensureUsers = lib.singleton {
+        name = cfg.settings.db.user;
+        ensurePermissions = {
+          "DATABASE ${cfg.settings.db.dbname}" = "ALL PRIVILEGES";
+        };
+      };
+      # This is only needed because the unix user invidious isn't the same as
+      # the database user. This tells postgres to map one to the other.
+      identMap = ''
+        invidious invidious ${cfg.settings.db.user}
+      '';
+      # And this specifically enables peer authentication for only this
+      # database, which allows passwordless authentication over the postgres
+      # unix socket for the user map given above.
+      authentication = ''
+        local ${cfg.settings.db.dbname} ${cfg.settings.db.user} peer map=invidious
+      '';
+    };
+
+    systemd.services.invidious-db-clean = {
+      description = "Invidious database cleanup";
+      documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ];
+      startAt = lib.mkDefault "weekly";
+      path = [ config.services.postgresql.package ];
+      script = ''
+        psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp"
+        psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos"
+      '';
+      serviceConfig = {
+        DynamicUser = true;
+        User = "invidious";
+      };
+    };
+
+    systemd.services.invidious = {
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+
+      serviceConfig = {
+        User = "invidious";
+      };
+    };
+  };
+
+  nginxConfig = lib.mkIf cfg.nginx.enable {
+    services.invidious.settings = {
+      https_only = config.services.nginx.virtualHosts.${cfg.domain}.forceSSL;
+      external_port = 80;
+    };
+
+    services.nginx = {
+      enable = true;
+      virtualHosts.${cfg.domain} = {
+        locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
+
+        enableACME = lib.mkDefault true;
+        forceSSL = lib.mkDefault true;
+      };
+    };
+
+    assertions = [{
+      assertion = cfg.domain != null;
+      message = "To use services.invidious.nginx, you need to set services.invidious.domain";
+    }];
+  };
+in
+{
+  options.services.invidious = {
+    enable = lib.mkEnableOption "Invidious";
+
+    package = lib.mkOption {
+      type = types.package;
+      default = pkgs.invidious;
+      defaultText = "pkgs.invidious";
+      description = "The Invidious package to use.";
+    };
+
+    settings = lib.mkOption {
+      type = settingsFormat.type;
+      default = { };
+      description = ''
+        The settings Invidious should use.
+
+        See <link xlink:href="https://github.com/iv-org/invidious/blob/master/config/config.example.yml">config.example.yml</link> for a list of all possible options.
+      '';
+    };
+
+    extraSettingsFile = lib.mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        A file including Invidious settings.
+
+        It gets merged with the setttings specified in <option>services.invidious.settings</option>
+        and can be used to store secrets like <literal>hmac_key</literal> outside of the nix store.
+      '';
+    };
+
+    # This needs to be outside of settings to avoid infinite recursion
+    # (determining if nginx should be enabled and therefore the settings
+    # modified).
+    domain = lib.mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The FQDN Invidious is reachable on.
+
+        This is used to configure nginx and for building absolute URLs.
+      '';
+    };
+
+    port = lib.mkOption {
+      type = types.port;
+      # Default from https://docs.invidious.io/Configuration.md
+      default = 3000;
+      description = ''
+        The port Invidious should listen on.
+
+        To allow access from outside,
+        you can use either <option>services.invidious.nginx</option>
+        or add <literal>config.services.invidious.port</literal> to <option>networking.firewall.allowedTCPPorts</option>.
+      '';
+    };
+
+    database = {
+      createLocally = lib.mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to create a local database with PostgreSQL.
+        '';
+      };
+
+      host = lib.mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The database host Invidious should use.
+
+          If <literal>null</literal>, the local unix socket is used. Otherwise
+          TCP is used.
+        '';
+      };
+
+      port = lib.mkOption {
+        type = types.port;
+        default = options.services.postgresql.port.default;
+        description = ''
+          The port of the database Invidious should use.
+
+          Defaults to the the default postgresql port.
+        '';
+      };
+
+      passwordFile = lib.mkOption {
+        type = types.nullOr types.str;
+        apply = lib.mapNullable toString;
+        default = null;
+        description = ''
+          Path to file containing the database password.
+        '';
+      };
+    };
+
+    nginx.enable = lib.mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to configure nginx as a reverse proxy for Invidious.
+
+        It serves it under the domain specified in <option>services.invidious.settings.domain</option> with enabled TLS and ACME.
+        Further configuration can be done through <option>services.nginx.virtualHosts.''${config.services.invidious.settings.domain}.*</option>,
+        which can also be used to disable AMCE and TLS.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    serviceConfig
+    localDatabaseConfig
+    nginxConfig
+  ]);
+}
diff --git a/nixos/modules/services/web-apps/isso.nix b/nixos/modules/services/web-apps/isso.nix
index d05a99a3eedc4..4c01781a6a2b3 100644
--- a/nixos/modules/services/web-apps/isso.nix
+++ b/nixos/modules/services/web-apps/isso.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkEnableOption mkIf mkOption types literalExample;
+  inherit (lib) mkEnableOption mkIf mkOption types literalExpression;
 
   cfg = config.services.isso;
 
@@ -31,7 +31,7 @@ in {
           freeformType = settingsFormat.type;
         };
 
-        example = literalExample ''
+        example = literalExpression ''
           {
             general = {
               host = "http://localhost";
diff --git a/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix
index 4f181257ef7cd..83cf224f7d27d 100644
--- a/nixos/modules/services/web-apps/jirafeau.nix
+++ b/nixos/modules/services/web-apps/jirafeau.nix
@@ -84,18 +84,19 @@ in
       type = types.submodule
         (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
       default = {};
-      example = {
-        serverAliases = [ "wiki.\${config.networking.domain}" ];
-      };
+      example = literalExpression ''
+        {
+          serverAliases = [ "wiki.''${config.networking.domain}" ];
+        }
+      '';
       description = "Extra configuration for the nginx virtual host of Jirafeau.";
     };
 
     package = mkOption {
       type = types.package;
       default = pkgs.jirafeau;
-      defaultText = "pkgs.jirafeau";
+      defaultText = literalExpression "pkgs.jirafeau";
       description = "Jirafeau package to use";
-      example = "pkgs.jirafeau";
     };
 
     poolConfig = mkOption {
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 997604754e422..2f1c4acec1e8b 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -37,6 +37,11 @@ let
       focus = "focus.${cfg.hostName}";
     };
     bosh = "//${cfg.hostName}/http-bind";
+    websocket = "wss://${cfg.hostName}/xmpp-websocket";
+
+    fileRecordingsEnabled = true;
+    liveStreamingEnabled = true;
+    hiddenDomain = "recorder.${cfg.hostName}";
   };
 in
 {
@@ -47,14 +52,14 @@ in
       type = str;
       example = "meet.example.org";
       description = ''
-        Hostname of the Jitsi Meet instance.
+        FQDN of the Jitsi Meet instance.
       '';
     };
 
     config = mkOption {
       type = attrs;
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           enableWelcomePage = false;
           defaultLang = "fi";
@@ -81,7 +86,7 @@ in
     interfaceConfig = mkOption {
       type = attrs;
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           SHOW_JITSI_WATERMARK = false;
           SHOW_WATERMARK_FOR_GUESTS = false;
@@ -129,6 +134,17 @@ in
       '';
     };
 
+    jibri.enable = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        Whether to enable a Jibri instance and configure it to connect to Prosody.
+
+        Additional configuration is possible with <option>services.jibri</option>, and
+        <option>services.jibri.finalizeScript</option> is especially useful.
+      '';
+    };
+
     nginx.enable = mkOption {
       type = bool;
       default = true;
@@ -143,6 +159,8 @@ in
       '';
     };
 
+    caddy.enable = mkEnableOption "Whether to enablle caddy reverse proxy to expose jitsi-meet";
+
     prosody.enable = mkOption {
       type = bool;
       default = true;
@@ -163,7 +181,9 @@ in
         ping = mkDefault true;
         roster = mkDefault true;
         saslauth = mkDefault true;
+        smacks = mkDefault true;
         tls = mkDefault true;
+        websocket = mkDefault true;
       };
       muc = [
         {
@@ -185,12 +205,17 @@ in
           #-- muc_room_cache_size = 1000
         }
       ];
-      extraModules = [ "pubsub" ];
+      extraModules = [ "pubsub" "smacks" ];
       extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ];
-      extraConfig = mkAfter ''
+      extraConfig = lib.mkMerge [ (mkAfter ''
         Component "focus.${cfg.hostName}" "client_proxy"
           target_address = "focus@auth.${cfg.hostName}"
-      '';
+        '')
+        (mkBefore ''
+          cross_domain_websocket = true;
+          consider_websocket_secure = true;
+        '')
+      ];
       virtualHosts.${cfg.hostName} = {
         enabled = true;
         domain = cfg.hostName;
@@ -198,6 +223,10 @@ in
           authentication = "anonymous"
           c2s_require_encryption = false
           admins = { "focus@auth.${cfg.hostName}" }
+          smacks_max_unacked_stanzas = 5
+          smacks_hibernation_time = 60
+          smacks_max_hibernated_sessions = 1
+          smacks_max_old_sessions = 1
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -215,6 +244,14 @@ in
           key = "/var/lib/jitsi-meet/jitsi-meet.key";
         };
       };
+      virtualHosts."recorder.${cfg.hostName}" = {
+        enabled = true;
+        domain = "recorder.${cfg.hostName}";
+        extraConfig = ''
+          authentication = "internal_plain"
+          c2s_require_encryption = false
+        '';
+      };
     };
     systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable {
       EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
@@ -229,12 +266,13 @@ in
     systemd.services.jitsi-meet-init-secrets = {
       wantedBy = [ "multi-user.target" ];
       before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service");
+      path = [ config.services.prosody.package ];
       serviceConfig = {
         Type = "oneshot";
       };
 
       script = let
-        secrets = [ "jicofo-component-secret" "jicofo-user-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
+        secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-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
       ''
@@ -253,9 +291,11 @@ in
         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})"
-        ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
+        prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
+        prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
+        prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
+        prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
+        prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
 
         # generate self-signed certificates
         if [ ! -f /var/lib/jitsi-meet.crt ]; then
@@ -286,6 +326,11 @@ in
           rewrite ^/(.*)$ / break;
         '';
         locations."~ ^/([^/\\?&:'\"]+)$".tryFiles = "$uri @root_path";
+        locations."^~ /xmpp-websocket" = {
+          priority = 100;
+          proxyPass = "http://localhost:5280/xmpp-websocket";
+          proxyWebsockets = true;
+        };
         locations."=/http-bind" = {
           proxyPass = "http://localhost:5280/http-bind";
           extraConfig = ''
@@ -305,6 +350,42 @@ in
       };
     };
 
+    services.caddy = mkIf cfg.caddy.enable {
+      enable = mkDefault true;
+      virtualHosts.${cfg.hostName} = {
+        extraConfig =
+        let
+          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" {} ''
+            cp -R ${pkgs.jitsi-meet}/* .
+            for file in *.html **/*.html ; do
+              ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file
+            done
+            rm config.js
+            rm interface_config.js
+            cp -R . $out
+            cp ${overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig} $out/config.js
+            cp ${overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig ""} $out/interface_config.js
+            cp ./libs/external_api.min.js $out/external_api.js
+          '';
+        in ''
+          handle /http-bind {
+            header Host ${cfg.hostName}
+            reverse_proxy 127.0.0.1:5280
+          }
+          handle /xmpp-websocket {
+            reverse_proxy 127.0.0.1:5280
+          }
+          handle {
+            templates
+            root * ${templatedJitsiMeet}
+            try_files {path} {path}
+            try_files {path} /index.html
+            file_server
+          }
+        '';
+      };
+    };
+
     services.jitsi-videobridge = mkIf cfg.videobridge.enable {
       enable = true;
       xmppConfigs."localhost" = {
@@ -325,8 +406,43 @@ in
       userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
       bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
-      config = {
+      config = mkMerge [{
         "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true";
+      #} (lib.mkIf cfg.jibri.enable {
+       } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
+        "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}";
+        "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90";
+      })];
+    };
+
+    services.jibri = mkIf cfg.jibri.enable {
+      enable = true;
+
+      xmppEnvironments."jitsi-meet" = {
+        xmppServerHosts = [ "localhost" ];
+        xmppDomain = cfg.hostName;
+
+        control.muc = {
+          domain = "internal.${cfg.hostName}";
+          roomName = "JibriBrewery";
+          nickname = "jibri";
+        };
+
+        control.login = {
+          domain = "auth.${cfg.hostName}";
+          username = "jibri";
+          passwordFile = "/var/lib/jitsi-meet/jibri-auth-secret";
+        };
+
+        call.login = {
+          domain = "recorder.${cfg.hostName}";
+          username = "recorder";
+          passwordFile = "/var/lib/jitsi-meet/jibri-recorder-secret";
+        };
+
+        usageTimeout = "0";
+        disableCertificateVerification = true;
+        stripFromRoomDomain = "conference.";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index b1bea222c7f73..df8c7114102fd 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -210,6 +210,7 @@ in
     package = lib.mkOption {
       type = lib.types.package;
       default = pkgs.keycloak;
+      defaultText = lib.literalExpression "pkgs.keycloak";
       description = ''
         Keycloak package to use.
       '';
@@ -228,7 +229,7 @@ in
     extraConfig = lib.mkOption {
       type = lib.types.attrs;
       default = { };
-      example = lib.literalExample ''
+      example = lib.literalExpression ''
         {
           "subsystem=keycloak-server" = {
             "spi=hostname" = {
diff --git a/nixos/modules/services/web-apps/lemmy.md b/nixos/modules/services/web-apps/lemmy.md
new file mode 100644
index 0000000000000..e6599cd843e3e
--- /dev/null
+++ b/nixos/modules/services/web-apps/lemmy.md
@@ -0,0 +1,34 @@
+# Lemmy {#module-services-lemmy}
+
+Lemmy is a federated alternative to reddit in rust.
+
+## Quickstart {#module-services-lemmy-quickstart}
+
+the minimum to start lemmy is
+
+```nix
+services.lemmy = {
+  enable = true;
+  settings = {
+    hostname = "lemmy.union.rocks";
+    database.createLocally = true;
+  };
+  jwtSecretPath = "/run/secrets/lemmyJwt";
+  caddy.enable = true;
+}
+```
+
+(note that you can use something like agenix to get your secret jwt to the specified path)
+
+this will start the backend on port 8536 and the frontend on port 1234.
+It will expose your instance with a caddy reverse proxy to the hostname you've provided.
+Postgres will be initialized on that same instance automatically.
+
+## Usage {#module-services-lemmy-usage}
+
+On first connection you will be asked to define an admin user.
+
+## Missing {#module-services-lemmy-missing}
+
+- Exposing with nginx is not implemented yet.
+- This has been tested using a local database with a unix socket connection. Using different database settings will likely require modifications
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
new file mode 100644
index 0000000000000..7cd2357c45566
--- /dev/null
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -0,0 +1,236 @@
+{ lib, pkgs, config, ... }:
+with lib;
+let
+  cfg = config.services.lemmy;
+  settingsFormat = pkgs.formats.json { };
+in
+{
+  meta.maintainers = with maintainers; [ happysalada ];
+  # Don't edit the docbook xml directly, edit the md and generate it:
+  # `pandoc lemmy.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > lemmy.xml`
+  meta.doc = ./lemmy.xml;
+
+  options.services.lemmy = {
+
+    enable = mkEnableOption "lemmy a federated alternative to reddit in rust";
+
+    jwtSecretPath = mkOption {
+      type = types.path;
+      description = "Path to read the jwt secret from.";
+    };
+
+    ui = {
+      port = mkOption {
+        type = types.port;
+        default = 1234;
+        description = "Port where lemmy-ui should listen for incoming requests.";
+      };
+    };
+
+    caddy.enable = mkEnableOption "exposing lemmy with the caddy reverse proxy";
+
+    settings = mkOption {
+      default = { };
+      description = "Lemmy configuration";
+
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+
+        options.hostname = mkOption {
+          type = types.str;
+          default = null;
+          description = "The domain name of your instance (eg 'lemmy.ml').";
+        };
+
+        options.port = mkOption {
+          type = types.port;
+          default = 8536;
+          description = "Port where lemmy should listen for incoming requests.";
+        };
+
+        options.federation = {
+          enabled = mkEnableOption "activitypub federation";
+        };
+
+        options.captcha = {
+          enabled = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Enable Captcha.";
+          };
+          difficulty = mkOption {
+            type = types.enum [ "easy" "medium" "hard" ];
+            default = "medium";
+            description = "The difficultly of the captcha to solve.";
+          };
+        };
+
+        options.database.createLocally = mkEnableOption "creation of database on the instance";
+
+      };
+    };
+
+  };
+
+  config =
+    let
+      localPostgres = (cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql");
+    in
+    lib.mkIf cfg.enable {
+      services.lemmy.settings = (mapAttrs (name: mkDefault)
+        {
+          bind = "127.0.0.1";
+          tls_enabled = true;
+          pictrs_url = with config.services.pict-rs; "http://${address}:${toString port}";
+          actor_name_max_length = 20;
+
+          rate_limit.message = 180;
+          rate_limit.message_per_second = 60;
+          rate_limit.post = 6;
+          rate_limit.post_per_second = 600;
+          rate_limit.register = 3;
+          rate_limit.register_per_second = 3600;
+          rate_limit.image = 6;
+          rate_limit.image_per_second = 3600;
+        } // {
+        database = mapAttrs (name: mkDefault) {
+          user = "lemmy";
+          host = "/run/postgresql";
+          port = 5432;
+          database = "lemmy";
+          pool_size = 5;
+        };
+      });
+
+      services.postgresql = mkIf localPostgres {
+        enable = mkDefault true;
+      };
+
+      services.pict-rs.enable = true;
+
+      services.caddy = mkIf cfg.caddy.enable {
+        enable = mkDefault true;
+        virtualHosts."${cfg.settings.hostname}" = {
+          extraConfig = ''
+            handle_path /static/* {
+              root * ${pkgs.lemmy-ui}/dist
+              file_server
+            }
+            @for_backend {
+              path /api/* /pictrs/* feeds/* nodeinfo/*
+            }
+            handle @for_backend {
+              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
+            }
+            @post {
+              method POST
+            }
+            handle @post {
+              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
+            }
+            @jsonld {
+              header Accept "application/activity+json"
+              header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+            }
+            handle @jsonld {
+              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
+            }
+            handle {
+              reverse_proxy 127.0.0.1:${toString cfg.ui.port}
+            }
+          '';
+        };
+      };
+
+      assertions = [{
+        assertion = cfg.settings.database.createLocally -> localPostgres;
+        message = "if you want to create the database locally, you need to use a local database";
+      }];
+
+      systemd.services.lemmy = {
+        description = "Lemmy server";
+
+        environment = {
+          LEMMY_CONFIG_LOCATION = "/run/lemmy/config.hjson";
+
+          # Verify how this is used, and don't put the password in the nix store
+          LEMMY_DATABASE_URL = with cfg.settings.database;"postgres:///${database}?host=${host}";
+        };
+
+        documentation = [
+          "https://join-lemmy.org/docs/en/administration/from_scratch.html"
+          "https://join-lemmy.org/docs"
+        ];
+
+        wantedBy = [ "multi-user.target" ];
+
+        after = [ "pict-rs.service " ] ++ lib.optionals cfg.settings.database.createLocally [ "lemmy-postgresql.service" ];
+
+        requires = lib.optionals cfg.settings.database.createLocally [ "lemmy-postgresql.service" ];
+
+        # script is needed here since loadcredential is not accessible on ExecPreStart
+        script = ''
+          ${pkgs.coreutils}/bin/install -m 600 ${settingsFormat.generate "config.hjson" cfg.settings} /run/lemmy/config.hjson
+          jwtSecret="$(< $CREDENTIALS_DIRECTORY/jwt_secret )"
+          ${pkgs.jq}/bin/jq ".jwt_secret = \"$jwtSecret\"" /run/lemmy/config.hjson | ${pkgs.moreutils}/bin/sponge /run/lemmy/config.hjson
+          ${pkgs.lemmy-server}/bin/lemmy_server
+        '';
+
+        serviceConfig = {
+          DynamicUser = true;
+          RuntimeDirectory = "lemmy";
+          LoadCredential = "jwt_secret:${cfg.jwtSecretPath}";
+        };
+      };
+
+      systemd.services.lemmy-ui = {
+        description = "Lemmy ui";
+
+        environment = {
+          LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
+          LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
+          LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
+          LEMMY_HTTPS = "false";
+        };
+
+        documentation = [
+          "https://join-lemmy.org/docs/en/administration/from_scratch.html"
+          "https://join-lemmy.org/docs"
+        ];
+
+        wantedBy = [ "multi-user.target" ];
+
+        after = [ "lemmy.service" ];
+
+        requires = [ "lemmy.service" ];
+
+        serviceConfig = {
+          DynamicUser = true;
+          WorkingDirectory = "${pkgs.lemmy-ui}";
+          ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.lemmy-ui}/dist/js/server.js";
+        };
+      };
+
+      systemd.services.lemmy-postgresql = mkIf cfg.settings.database.createLocally {
+        description = "Lemmy postgresql db";
+        after = [ "postgresql.service" ];
+        partOf = [ "lemmy.service" ];
+        script = with cfg.settings.database; ''
+          PSQL() {
+            ${config.services.postgresql.package}/bin/psql --port=${toString cfg.settings.database.port} "$@"
+          }
+          # check if the database already exists
+          if ! PSQL -lqt | ${pkgs.coreutils}/bin/cut -d \| -f 1 | ${pkgs.gnugrep}/bin/grep -qw ${database} ; then
+            PSQL -tAc "CREATE ROLE ${user} WITH LOGIN;"
+            PSQL -tAc "CREATE DATABASE ${database} WITH OWNER ${user};"
+          fi
+        '';
+        serviceConfig = {
+          User = config.services.postgresql.superUser;
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+      };
+    };
+
+}
diff --git a/nixos/modules/services/web-apps/lemmy.xml b/nixos/modules/services/web-apps/lemmy.xml
new file mode 100644
index 0000000000000..0be9fb8aefa98
--- /dev/null
+++ b/nixos/modules/services/web-apps/lemmy.xml
@@ -0,0 +1,56 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-lemmy">
+  <title>Lemmy</title>
+  <para>
+    Lemmy is a federated alternative to reddit in rust.
+  </para>
+  <section xml:id="module-services-lemmy-quickstart">
+    <title>Quickstart</title>
+    <para>
+      the minimum to start lemmy is
+    </para>
+    <programlisting language="bash">
+services.lemmy = {
+  enable = true;
+  settings = {
+    hostname = &quot;lemmy.union.rocks&quot;;
+    database.createLocally = true;
+  };
+  jwtSecretPath = &quot;/run/secrets/lemmyJwt&quot;;
+  caddy.enable = true;
+}
+</programlisting>
+    <para>
+      (note that you can use something like agenix to get your secret
+      jwt to the specified path)
+    </para>
+    <para>
+      this will start the backend on port 8536 and the frontend on port
+      1234. It will expose your instance with a caddy reverse proxy to
+      the hostname you’ve provided. Postgres will be initialized on that
+      same instance automatically.
+    </para>
+  </section>
+  <section xml:id="module-services-lemmy-usage">
+    <title>Usage</title>
+    <para>
+      On first connection you will be asked to define an admin user.
+    </para>
+  </section>
+  <section xml:id="module-services-lemmy-missing">
+    <title>Missing</title>
+    <itemizedlist spacing="compact">
+      <listitem>
+        <para>
+          Exposing with nginx is not implemented yet.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          This has been tested using a local database with a unix socket
+          connection. Using different database settings will likely
+          require modifications
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index 56265e80957ed..5ccd742a303be 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -3,7 +3,7 @@
 let
 
   inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
-  inherit (lib) literalExample mapAttrs optional optionalString types;
+  inherit (lib) literalExpression mapAttrs optional optionalString types;
 
   cfg = config.services.limesurvey;
   fpm = config.services.phpfpm.pools.limesurvey;
@@ -51,7 +51,7 @@ in
       port = mkOption {
         type = types.int;
         default = if cfg.database.type == "pgsql" then 5442 else 3306;
-        defaultText = "3306";
+        defaultText = literalExpression "3306";
         description = "Database host port.";
       };
 
@@ -84,14 +84,14 @@ in
           else if pgsqlLocal then "/run/postgresql"
           else null
         ;
-        defaultText = "/run/mysqld/mysqld.sock";
+        defaultText = literalExpression "/run/mysqld/mysqld.sock";
         description = "Path to the unix socket file to use for authentication.";
       };
 
       createLocally = mkOption {
         type = types.bool;
         default = cfg.database.type == "mysql";
-        defaultText = "true";
+        defaultText = literalExpression "true";
         description = ''
           Create the database and database user locally.
           This currently only applies if database type "mysql" is selected.
@@ -101,7 +101,7 @@ in
 
     virtualHost = mkOption {
       type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-      example = literalExample ''
+      example = literalExpression ''
         {
           hostName = "survey.example.org";
           adminAddr = "webmaster@example.org";
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 5e24bd06ffdbe..1e3c7e53c175a 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -9,6 +9,13 @@ let
     RAILS_ENV = "production";
     NODE_ENV = "production";
 
+    # mastodon-web concurrency.
+    WEB_CONCURRENCY = toString cfg.webProcesses;
+    MAX_THREADS = toString cfg.webThreads;
+
+    # mastodon-streaming concurrency.
+    STREAMING_CLUSTER_NUM = toString cfg.streamingProcesses;
+
     DB_USER = cfg.database.user;
 
     REDIS_HOST = cfg.redis.host;
@@ -31,7 +38,7 @@ let
   // (if cfg.smtp.authenticate then { SMTP_LOGIN  = cfg.smtp.user; } else {})
   // cfg.extraConfig;
 
-  systemCallsList = [ "@clock" "@cpu-emulation" "@debug" "@keyring" "@module" "@mount" "@obsolete" "@raw-io" "@reboot" "@setuid" "@swap" ];
+  systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
 
   cfgService = {
     # User and group
@@ -43,6 +50,9 @@ let
     # Logs directory and mode
     LogsDirectory = "mastodon";
     LogsDirectoryMode = "0750";
+    # Proc filesystem
+    ProcSubset = "pid";
+    ProtectProc = "invisible";
     # Access write directories
     UMask = "0027";
     # Capabilities
@@ -67,6 +77,7 @@ let
     MemoryDenyWriteExecute = false;
     RestrictRealtime = true;
     RestrictSUIDSGID = true;
+    RemoveIPC = true;
     PrivateMounts = true;
     # System Call Filtering
     SystemCallArchitectures = "native";
@@ -146,18 +157,41 @@ in {
         type = lib.types.port;
         default = 55000;
       };
+      streamingProcesses = lib.mkOption {
+        description = ''
+          Processes used by the mastodon-streaming service.
+          Defaults to the number of CPU cores minus one.
+        '';
+        type = lib.types.nullOr lib.types.int;
+        default = null;
+      };
 
       webPort = lib.mkOption {
         description = "TCP port used by the mastodon-web service.";
         type = lib.types.port;
         default = 55001;
       };
+      webProcesses = lib.mkOption {
+        description = "Processes used by the mastodon-web service.";
+        type = lib.types.int;
+        default = 2;
+      };
+      webThreads = lib.mkOption {
+        description = "Threads per process used by the mastodon-web service.";
+        type = lib.types.int;
+        default = 5;
+      };
 
       sidekiqPort = lib.mkOption {
-        description = "TCP port used by the mastodon-sidekiq service";
+        description = "TCP port used by the mastodon-sidekiq service.";
         type = lib.types.port;
         default = 55002;
       };
+      sidekiqThreads = lib.mkOption {
+        description = "Worker threads used by the mastodon-sidekiq service.";
+        type = lib.types.int;
+        default = 25;
+      };
 
       vapidPublicKeyFile = lib.mkOption {
         description = ''
@@ -314,7 +348,7 @@ in {
         authenticate = lib.mkOption {
           description = "Authenticate with the SMTP server using username and password.";
           type = lib.types.bool;
-          default = true;
+          default = false;
         };
 
         host = lib.mkOption {
@@ -369,7 +403,7 @@ in {
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.mastodon;
-        defaultText = "pkgs.mastodon";
+        defaultText = lib.literalExpression "pkgs.mastodon";
         description = "Mastodon package to use.";
       };
 
@@ -434,7 +468,7 @@ in {
         Type = "oneshot";
         WorkingDirectory = cfg.package;
         # System Call Filtering
-        SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
       } // cfgService;
 
       after = [ "network.target" ];
@@ -461,7 +495,7 @@ in {
         EnvironmentFile = "/var/lib/mastodon/.secrets_env";
         WorkingDirectory = cfg.package;
         # System Call Filtering
-        SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
       } // cfgService;
       after = [ "mastodon-init-dirs.service" "network.target" ] ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else []);
       wantedBy = [ "multi-user.target" ];
@@ -487,7 +521,7 @@ in {
         RuntimeDirectory = "mastodon-streaming";
         RuntimeDirectoryMode = "0750";
         # System Call Filtering
-        SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@privileged" "@resources" ]);
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
       } // cfgService;
     };
 
@@ -511,7 +545,7 @@ in {
         RuntimeDirectory = "mastodon-web";
         RuntimeDirectoryMode = "0750";
         # System Call Filtering
-        SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
       } // cfgService;
       path = with pkgs; [ file imagemagick ffmpeg ];
     };
@@ -524,15 +558,16 @@ in {
       wantedBy = [ "multi-user.target" ];
       environment = env // {
         PORT = toString(cfg.sidekiqPort);
+        DB_POOL = toString cfg.sidekiqThreads;
       };
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/sidekiq -c 25 -r ${cfg.package}";
+        ExecStart = "${cfg.package}/bin/sidekiq -c ${toString cfg.sidekiqThreads} -r ${cfg.package}";
         Restart = "always";
         RestartSec = 20;
         EnvironmentFile = "/var/lib/mastodon/.secrets_env";
         WorkingDirectory = cfg.package;
         # System Call Filtering
-        SystemCallFilter = "~" + lib.concatStringsSep " " systemCallsList;
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
       } // cfgService;
       path = with pkgs; [ file imagemagick ffmpeg ];
     };
@@ -565,6 +600,7 @@ in {
 
     services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
       enable = true;
+      hostname = lib.mkDefault "${cfg.localDomain}";
     };
     services.redis = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") {
       enable = true;
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index 79a0354e22b42..eba55e7e9befa 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -24,6 +24,7 @@ in {
     (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
     (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
     (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ])
+    (mkRenamedOptionModule [ "services" "matomo" "periodicArchiveProcessingUrl" ] [ "services" "matomo" "hostname" ])
   ];
 
   options = {
@@ -48,7 +49,7 @@ in {
           as they don't get backported if they are not security-relevant.
         '';
         default = pkgs.matomo;
-        defaultText = "pkgs.matomo";
+        defaultText = literalExpression "pkgs.matomo";
       };
 
       webServerUser = mkOption {
@@ -77,7 +78,7 @@ in {
         '';
       };
 
-      periodicArchiveProcessingUrl = mkOption {
+      hostname = mkOption {
         type = types.str;
         default = "${user}.${fqdn}";
         example = "matomo.yourdomain.org";
@@ -100,13 +101,15 @@ in {
         )
         );
         default = null;
-        example = {
-          serverAliases = [
-            "matomo.\${config.networking.domain}"
-            "stats.\${config.networking.domain}"
-          ];
-          enableACME = false;
-        };
+        example = literalExpression ''
+          {
+            serverAliases = [
+              "matomo.''${config.networking.domain}"
+              "stats.''${config.networking.domain}"
+            ];
+            enableACME = false;
+          }
+        '';
         description = ''
             With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
             Either this option or the webServerUser option is mandatory.
@@ -168,6 +171,19 @@ in {
         fi
         chown -R ${user}:${user} ${dataDir}
         chmod -R ug+rwX,o-rwx ${dataDir}
+
+        if [ -e ${dataDir}/current-package ]; then
+          CURRENT_PACKAGE=$(readlink ${dataDir}/current-package)
+          NEW_PACKAGE=${cfg.package}
+          if [ "$CURRENT_PACKAGE" != "$NEW_PACKAGE" ]; then
+            # keeping tmp arround between upgrades seems to bork stuff, so delete it
+            rm -rf ${dataDir}/tmp
+          fi
+        elif [ -e ${dataDir}/tmp ]; then
+          # upgrade from 4.4.1
+          rm -rf ${dataDir}/tmp
+        fi
+        ln -sfT ${cfg.package} ${dataDir}/current-package
         '';
       script = ''
             # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
@@ -200,7 +216,7 @@ in {
         UMask = "0007";
         CPUSchedulingPolicy = "idle";
         IOSchedulingClass = "idle";
-        ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${cfg.periodicArchiveProcessingUrl}";
+        ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${cfg.hostname}";
       };
     };
 
@@ -256,7 +272,7 @@ in {
       # References:
       # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
       # https://github.com/perusio/piwik-nginx
-      "${user}.${fqdn}" = mkMerge [ cfg.nginx {
+      "${cfg.hostname}" = mkMerge [ cfg.nginx {
         # don't allow to override the root easily, as it will almost certainly break Matomo.
         # disadvantage: not shown as default in docs.
         root = mkForce "${cfg.package}/share";
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 1db1652022a34..977b6f60b230e 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -3,7 +3,7 @@
 let
 
   inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
-  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionals optionalString types;
+  inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionals optionalString types;
 
   cfg = config.services.mediawiki;
   fpm = config.services.phpfpm.pools.mediawiki;
@@ -176,6 +176,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.mediawiki;
+        defaultText = literalExpression "pkgs.mediawiki";
         description = "Which MediaWiki package to use.";
       };
 
@@ -219,7 +220,7 @@ in
 
           Use <literal>null</literal> instead of path to enable extensions that are part of MediaWiki.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             Matomo = pkgs.fetchzip {
               url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
@@ -286,14 +287,14 @@ in
         socket = mkOption {
           type = types.nullOr types.path;
           default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null;
-          defaultText = "/run/mysqld/mysqld.sock";
+          defaultText = literalExpression "/run/mysqld/mysqld.sock";
           description = "Path to the unix socket file to use for authentication.";
         };
 
         createLocally = mkOption {
           type = types.bool;
           default = cfg.database.type == "mysql";
-          defaultText = "true";
+          defaultText = literalExpression "true";
           description = ''
             Create the database and database user locally.
             This currently only applies if database type "mysql" is selected.
@@ -303,7 +304,7 @@ in
 
       virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-        example = literalExample ''
+        example = literalExpression ''
           {
             hostName = "mediawiki.example.org";
             adminAddr = "webmaster@example.org";
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 1bbadafa20783..026bde2a92df3 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -35,7 +35,7 @@ in
 
       config = mkOption {
         type = types.attrsOf types.str;
-        example = literalExample ''
+        example = literalExpression ''
           {
             CLEANUP_FREQUENCY = "48";
             LISTEN_ADDR = "localhost:8080";
diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix
index 7a54255a46efc..efb73124a2373 100644
--- a/nixos/modules/services/web-apps/moinmoin.nix
+++ b/nixos/modules/services/web-apps/moinmoin.nix
@@ -151,7 +151,7 @@ in
           webHost = mkDefault name;
         };
       }));
-      example = literalExample ''
+      example = literalExpression ''
         {
           "mywiki" = {
             siteName = "Example Wiki";
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index c854e084e14d6..6f5cfa2e34815 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionalString;
+  inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionalString;
 
   cfg = config.services.moodle;
   fpm = config.services.phpfpm.pools.moodle;
@@ -67,7 +67,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.moodle;
-      defaultText = "pkgs.moodle";
+      defaultText = literalExpression "pkgs.moodle";
       description = "The Moodle package to use.";
     };
 
@@ -100,7 +100,7 @@ in
           mysql = 3306;
           pgsql = 5432;
         }.${cfg.database.type};
-        defaultText = "3306";
+        defaultText = literalExpression "3306";
       };
 
       name = mkOption {
@@ -131,7 +131,7 @@ in
           if mysqlLocal then "/run/mysqld/mysqld.sock"
           else if pgsqlLocal then "/run/postgresql"
           else null;
-        defaultText = "/run/mysqld/mysqld.sock";
+        defaultText = literalExpression "/run/mysqld/mysqld.sock";
         description = "Path to the unix socket file to use for authentication.";
       };
 
@@ -144,7 +144,7 @@ in
 
     virtualHost = mkOption {
       type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-      example = literalExample ''
+      example = literalExpression ''
         {
           hostName = "moodle.example.org";
           adminAddr = "webmaster@example.org";
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index ba5f6582cbec1..04ec7888950d5 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -6,7 +6,9 @@ let
   cfg = config.services.nextcloud;
   fpm = config.services.phpfpm.pools.nextcloud;
 
-  phpPackage = pkgs.php74.buildEnv {
+  inherit (cfg) datadir;
+
+  phpPackage = cfg.phpPackage.buildEnv {
     extensions = { enabled, all }:
       (with all;
         enabled
@@ -40,7 +42,7 @@ let
     if [[ "$USER" != nextcloud ]]; then
       sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
     fi
-    export NEXTCLOUD_CONFIG_DIR="${cfg.home}/config"
+    export NEXTCLOUD_CONFIG_DIR="${datadir}/config"
     $sudo \
       ${phpPackage}/bin/php \
       occ "$@"
@@ -51,6 +53,12 @@ let
 in {
 
   imports = [
+    (mkRemovedOptionModule [ "services" "nextcloud" "config" "adminpass" ] ''
+      Please use `services.nextcloud.config.adminpassFile' instead!
+    '')
+    (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbpass" ] ''
+      Please use `services.nextcloud.config.dbpassFile' instead!
+    '')
     (mkRemovedOptionModule [ "services" "nextcloud" "nginx" "enable" ] ''
       The nextcloud module supports `nginx` as reverse-proxy by default and doesn't
       support other reverse-proxies officially.
@@ -79,6 +87,59 @@ in {
       default = "/var/lib/nextcloud";
       description = "Storage path of nextcloud.";
     };
+    datadir = mkOption {
+      type = types.str;
+      defaultText = "config.services.nextcloud.home";
+      description = ''
+        Data storage path of nextcloud.  Will be <xref linkend="opt-services.nextcloud.home" /> by default.
+        This folder will be populated with a config.php and data folder which contains the state of the instance (excl the database).";
+      '';
+      example = "/mnt/nextcloud-file";
+    };
+    extraApps = mkOption {
+      type = types.attrsOf types.package;
+      default = { };
+      description = ''
+        Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp.
+        The appid must be identical to the "id" value in the apps appinfo/info.xml.
+        Using this will disable the appstore to prevent Nextcloud from updating these apps (see <xref linkend="opt-services.nextcloud.appstoreEnable" />).
+      '';
+      example = literalExpression ''
+        {
+          maps = pkgs.fetchNextcloudApp {
+            name = "maps";
+            sha256 = "007y80idqg6b6zk6kjxg4vgw0z8fsxs9lajnv49vv1zjy6jx2i1i";
+            url = "https://github.com/nextcloud/maps/releases/download/v0.1.9/maps-0.1.9.tar.gz";
+            version = "0.1.9";
+          };
+          phonetrack = pkgs.fetchNextcloudApp {
+            name = "phonetrack";
+            sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc";
+            url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz";
+            version = "0.6.9";
+          };
+        }
+        '';
+    };
+    extraAppsEnable = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Automatically enable the apps in <xref linkend="opt-services.nextcloud.extraApps" /> every time nextcloud starts.
+        If set to false, apps need to be enabled in the Nextcloud user interface or with nextcloud-occ app:enable.
+      '';
+    };
+    appstoreEnable = mkOption {
+      type = types.nullOr types.bool;
+      default = null;
+      example = true;
+      description = ''
+        Allow the installation of apps and app updates from the store.
+        Enabled by default unless there are packages in <xref linkend="opt-services.nextcloud.extraApps" />.
+        Set to true to force enable the store even if <xref linkend="opt-services.nextcloud.extraApps" /> is used.
+        Set to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting.
+      '';
+    };
     logLevel = mkOption {
       type = types.ints.between 0 4;
       default = 2;
@@ -92,7 +153,15 @@ in {
     package = mkOption {
       type = types.package;
       description = "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud20" "nextcloud21" "nextcloud22" ];
+      relatedPackages = [ "nextcloud21" "nextcloud22" ];
+    };
+    phpPackage = mkOption {
+      type = types.package;
+      relatedPackages = [ "php74" "php80" ];
+      defaultText = "pkgs.php";
+      description = ''
+        PHP package to use for Nextcloud.
+      '';
     };
 
     maxUploadSize = mkOption {
@@ -126,14 +195,14 @@ in {
     phpExtraExtensions = mkOption {
       type = with types; functionTo (listOf package);
       default = all: [];
-      defaultText = "all: []";
+      defaultText = literalExpression "all: []";
       description = ''
         Additional PHP extensions to use for nextcloud.
         By default, only extensions necessary for a vanilla nextcloud installation are enabled,
         but you may choose from the list of available extensions and add further ones.
         This is sometimes necessary to be able to install a certain nextcloud app that has additional requirements.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         all: [ all.pdlib all.bz2 ]
       '';
     };
@@ -198,14 +267,6 @@ in {
         default = "nextcloud";
         description = "Database user.";
       };
-      dbpass = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Database password.  Use <literal>dbpassFile</literal> to avoid this
-          being world-readable in the <literal>/nix/store</literal>.
-        '';
-      };
       dbpassFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -238,17 +299,8 @@ in {
         default = "root";
         description = "Admin username.";
       };
-      adminpass = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Admin password.  Use <literal>adminpassFile</literal> to avoid this
-          being world-readable in the <literal>/nix/store</literal>.
-        '';
-      };
       adminpassFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
+        type = types.str;
         description = ''
           The full path to a file that contains the admin's password. Must be
           readable by user <literal>nextcloud</literal>.
@@ -304,14 +356,98 @@ in {
           phone-numbers.
         '';
       };
+
+      objectstore = {
+        s3 = {
+          enable = mkEnableOption ''
+            S3 object storage as primary storage.
+
+            This mounts a bucket on an Amazon S3 object storage or compatible
+            implementation into the virtual filesystem.
+
+            Further details about this feature can be found in the
+            <link xlink:href="https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html">upstream documentation</link>.
+          '';
+          bucket = mkOption {
+            type = types.str;
+            example = "nextcloud";
+            description = ''
+              The name of the S3 bucket.
+            '';
+          };
+          autocreate = mkOption {
+            type = types.bool;
+            description = ''
+              Create the objectstore if it does not exist.
+            '';
+          };
+          key = mkOption {
+            type = types.str;
+            example = "EJ39ITYZEUH5BGWDRUFY";
+            description = ''
+              The access key for the S3 bucket.
+            '';
+          };
+          secretFile = mkOption {
+            type = types.str;
+            example = "/var/nextcloud-objectstore-s3-secret";
+            description = ''
+              The full path to a file that contains the access secret. Must be
+              readable by user <literal>nextcloud</literal>.
+            '';
+          };
+          hostname = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "example.com";
+            description = ''
+              Required for some non-Amazon implementations.
+            '';
+          };
+          port = mkOption {
+            type = types.nullOr types.port;
+            default = null;
+            description = ''
+              Required for some non-Amazon implementations.
+            '';
+          };
+          useSsl = mkOption {
+            type = types.bool;
+            default = true;
+            description = ''
+              Use SSL for objectstore access.
+            '';
+          };
+          region = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "REGION";
+            description = ''
+              Required for some non-Amazon implementations.
+            '';
+          };
+          usePathStyle = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Required for some non-Amazon S3 implementations.
+
+              Ordinarily, requests will be made with
+              <literal>http://bucket.hostname.domain/</literal>, but with path style
+              enabled requests are made with
+              <literal>http://hostname.domain/bucket</literal> instead.
+            '';
+          };
+        };
+      };
     };
 
     enableImagemagick = mkEnableOption ''
-        Whether to load the ImageMagick module into PHP.
+        the ImageMagick module for PHP.
         This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
         You may want to disable it for increased security. In that case, previews will still be available
         for some images (e.g. JPEG and PNG).
-        See https://github.com/nextcloud/server/issues/13099
+        See <link xlink:href="https://github.com/nextcloud/server/issues/13099" />.
     '' // {
       default = true;
     };
@@ -371,20 +507,7 @@ in {
   };
 
   config = mkIf cfg.enable (mkMerge [
-    { assertions = let acfg = cfg.config; in [
-        { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
-          message = "Please specify no more than one of dbpass or dbpassFile";
-        }
-        { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
-            && !(acfg.adminpass != null && acfg.adminpassFile != null));
-          message = "Please specify exactly one of adminpass or adminpassFile";
-        }
-        { assertion = versionOlder cfg.package.version "21" -> cfg.config.defaultPhoneRegion == null;
-          message = "The `defaultPhoneRegion'-setting is only supported for Nextcloud >=21!";
-        }
-      ];
-
-      warnings = let
+    { warnings = let
         latest = 22;
         upgradeWarning = major: nixos:
           ''
@@ -399,13 +522,38 @@ in {
             The package can be upgraded by explicitly declaring the service-option
             `services.nextcloud.package`.
           '';
+
+        # FIXME(@Ma27) remove as soon as nextcloud properly supports
+        # mariadb >=10.6.
+        isUnsupportedMariadb =
+          # All currently supported Nextcloud versions are affected.
+          (versionOlder cfg.package.version "23")
+          # This module uses mysql
+          && (cfg.config.dbtype == "mysql")
+          # MySQL is managed via NixOS
+          && config.services.mysql.enable
+          # We're using MariaDB
+          && (getName config.services.mysql.package) == "mariadb-server"
+          # MariaDB is at least 10.6 and thus not supported
+          && (versionAtLeast (getVersion config.services.mysql.package) "10.6");
+
       in (optional (cfg.poolConfig != null) ''
           Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
           Please migrate your configuration to config.services.nextcloud.poolSettings.
         '')
-        ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.05"))
         ++ (optional (versionOlder cfg.package.version "21") (upgradeWarning 20 "21.05"))
-        ++ (optional (versionOlder cfg.package.version "22") (upgradeWarning 21 "21.11"));
+        ++ (optional (versionOlder cfg.package.version "22") (upgradeWarning 21 "21.11"))
+        ++ (optional isUnsupportedMariadb ''
+            You seem to be using MariaDB at an unsupported version (i.e. at least 10.6)!
+            Please note that this isn't supported officially by Nextcloud. You can either
+
+            * Switch to `pkgs.mysql`
+            * Downgrade MariaDB to at least 10.5
+            * Work around Nextcloud's problems by specifying `innodb_read_only_compressed=0`
+
+            For further context, please read
+            https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/15
+          '');
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -419,16 +567,26 @@ in {
           # This versionOlder statement remains set to 21.03 for backwards compatibility.
           # See https://github.com/NixOS/nixpkgs/pull/108899 and
           # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
-          else if versionOlder stateVersion "21.03" then nextcloud19
+          # FIXME(@Ma27) remove this else-if as soon as 21.05 is EOL! This is only here
+          # to ensure that users who are on Nextcloud 19 with a stateVersion <21.05 with
+          # no explicit services.nextcloud.package don't upgrade to v21 by accident (
+          # nextcloud20 throws an eval-error because it's dropped).
+          else if versionOlder stateVersion "21.03" then nextcloud20
           else if versionOlder stateVersion "21.11" then nextcloud21
           else nextcloud22
         );
+
+      services.nextcloud.datadir = mkOptionDefault config.services.nextcloud.home;
+
+      services.nextcloud.phpPackage =
+        if versionOlder cfg.package.version "21" then pkgs.php74
+        else pkgs.php80;
     }
 
     { systemd.timers.nextcloud-cron = {
         wantedBy = [ "timers.target" ];
         timerConfig.OnBootSec = "5m";
-        timerConfig.OnUnitActiveSec = "15m";
+        timerConfig.OnUnitActiveSec = "5m";
         timerConfig.Unit = "nextcloud-cron.service";
       };
 
@@ -441,14 +599,39 @@ in {
         nextcloud-setup = let
           c = cfg.config;
           writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
+          requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable;
+          objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable ''
+            'objectstore' => [
+              'class' => '\\OC\\Files\\ObjectStore\\S3',
+              'arguments' => [
+                'bucket' => '${s3.bucket}',
+                'autocreate' => ${boolToString s3.autocreate},
+                'key' => '${s3.key}',
+                'secret' => nix_read_secret('${s3.secretFile}'),
+                ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
+                ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
+                'use_ssl' => ${boolToString s3.useSsl},
+                ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
+                'use_path_style' => ${boolToString s3.usePathStyle},
+              ],
+            ]
+          '';
+
+          showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {};
+          renderedAppStoreSetting =
+            let
+              x = cfg.appstoreEnable;
+            in
+              if x == null then "false"
+              else boolToString x;
+
           overrideConfig = pkgs.writeText "nextcloud-config.php" ''
             <?php
-            ${optionalString (c.dbpassFile != null) ''
-              function nix_read_pwd() {
-                $file = "${c.dbpassFile}";
+            ${optionalString requiresReadSecretFunction ''
+              function nix_read_secret($file) {
                 if (!file_exists($file)) {
                   throw new \RuntimeException(sprintf(
-                    "Cannot start Nextcloud, dbpass file %s set by NixOS doesn't seem to "
+                    "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to "
                     . "exist! Please make sure that the file exists and has appropriate "
                     . "permissions for user & group 'nextcloud'!",
                     $file
@@ -460,10 +643,12 @@ in {
             ''}
             $CONFIG = [
               'apps_paths' => [
+                ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"}
                 [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
                 [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
               ],
-              'datadirectory' => '${cfg.home}/data',
+              ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
+              'datadirectory' => '${datadir}/data',
               'skeletondirectory' => '${cfg.skeletonDirectory}',
               ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
               'log_type' => 'syslog',
@@ -474,23 +659,26 @@ in {
               ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
               ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
               ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
-              ${optionalString (c.dbpass != null) "'dbpassword' => '${c.dbpass}',"}
-              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"}
+              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('${c.dbpassFile}'),"}
               'dbtype' => '${c.dbtype}',
               'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
               'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
               ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"}
+              ${objectstoreConfig}
             ];
           '';
           occInstallCmd = let
-            dbpass = if c.dbpassFile != null
-              then ''"$(<"${toString c.dbpassFile}")"''
-              else if c.dbpass != null
-              then ''"${toString c.dbpass}"''
-              else ''""'';
-            adminpass = if c.adminpassFile != null
-              then ''"$(<"${toString c.adminpassFile}")"''
-              else ''"${toString c.adminpass}"'';
+            mkExport = { arg, value }: "export ${arg}=${value}";
+            dbpass = {
+              arg = "DBPASS";
+              value = if c.dbpassFile != null
+                then ''"$(<"${toString c.dbpassFile}")"''
+                else ''""'';
+            };
+            adminpass = {
+              arg = "ADMINPASS";
+              value = ''"$(<"${toString c.adminpassFile}")"'';
+            };
             installFlags = concatStringsSep " \\\n    "
               (mapAttrsToList (k: v: "${k} ${toString v}") {
               "--database" = ''"${c.dbtype}"'';
@@ -501,12 +689,14 @@ in {
               ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
               ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
               ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
-              "--database-pass" = dbpass;
+              "--database-pass" = "\$${dbpass.arg}";
               "--admin-user" = ''"${c.adminuser}"'';
-              "--admin-pass" = adminpass;
-              "--data-dir" = ''"${cfg.home}/data"'';
+              "--admin-pass" = "\$${adminpass.arg}";
+              "--data-dir" = ''"${datadir}/data"'';
             });
           in ''
+            ${mkExport dbpass}
+            ${mkExport adminpass}
             ${occ}/bin/nextcloud-occ maintenance:install \
                 ${installFlags}
           '';
@@ -533,22 +723,26 @@ in {
                 exit 1
               fi
             ''}
-            ${optionalString (c.adminpassFile != null) ''
-              if [ ! -r "${c.adminpassFile}" ]; then
-                echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
-                exit 1
-              fi
-              if [ -z "$(<${c.adminpassFile})" ]; then
-                echo "adminpassFile ${c.adminpassFile} is empty!"
-                exit 1
-              fi
-            ''}
+            if [ ! -r "${c.adminpassFile}" ]; then
+              echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
+              exit 1
+            fi
+            if [ -z "$(<${c.adminpassFile})" ]; then
+              echo "adminpassFile ${c.adminpassFile} is empty!"
+              exit 1
+            fi
 
             ln -sf ${cfg.package}/apps ${cfg.home}/
 
+            # Install extra apps
+            ln -sfT \
+              ${pkgs.linkFarm "nix-apps"
+                (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps)} \
+              ${cfg.home}/nix-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
+            for dir in ${datadir}/config ${datadir}/data ${cfg.home}/store-apps ${cfg.home}/nix-apps; do
               if [ ! -e $dir ]; then
                 install -o nextcloud -g nextcloud -d $dir
               elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then
@@ -556,23 +750,29 @@ in {
               fi
             done
 
-            ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
+            ln -sf ${overrideConfig} ${datadir}/config/override.config.php
 
             # Do not install if already installed
-            if [[ ! -e ${cfg.home}/config/config.php ]]; then
+            if [[ ! -e ${datadir}/config/config.php ]]; then
               ${occInstallCmd}
             fi
 
             ${occ}/bin/nextcloud-occ upgrade
 
             ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
+
+            ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
+                # Try to enable apps (don't fail when one of them cannot be enabled , eg. due to incompatible version)
+                ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
+            ''}
+
             ${occSetTrustedDomainsCmd}
           '';
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
         };
         nextcloud-cron = {
-          environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+          environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
           serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php";
@@ -591,7 +791,7 @@ in {
           group = "nextcloud";
           phpPackage = phpPackage;
           phpEnv = {
-            NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+            NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
             PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
           };
           settings = mapAttrs (name: mkDefault) {
@@ -641,6 +841,10 @@ in {
             priority = 201;
             extraConfig = "root ${cfg.home};";
           };
+          "~ ^/nix-apps" = {
+            priority = 201;
+            extraConfig = "root ${cfg.home};";
+          };
           "^~ /.well-known" = {
             priority = 210;
             extraConfig = ''
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index ed84487d233ad..9d9cb8dfb3f28 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -237,6 +237,12 @@
    Some apps may require extra PHP extensions to be installed.
    This can be configured with the <xref linkend="opt-services.nextcloud.phpExtraExtensions" /> setting.
   </para>
+
+  <para>
+   Alternatively, extra apps can also be declared with the <xref linkend="opt-services.nextcloud.extraApps" /> setting.
+   When using this setting, apps can no longer be managed statefully because this can lead to Nextcloud updating apps
+   that are managed by Nix. If you want automatic updates it is recommended that you use web interface to install apps.
+  </para>
  </section>
 
  <section xml:id="module-services-nextcloud-maintainer-info">
diff --git a/nixos/modules/services/web-apps/nexus.nix b/nixos/modules/services/web-apps/nexus.nix
index d4d507362c97b..dc50a06705f36 100644
--- a/nixos/modules/services/web-apps/nexus.nix
+++ b/nixos/modules/services/web-apps/nexus.nix
@@ -16,6 +16,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.nexus;
+        defaultText = literalExpression "pkgs.nexus";
         description = "Package which runs Nexus3";
       };
 
@@ -70,6 +71,27 @@ in
           -Dkaraf.startLocalConsole=false
           -Djava.endorsed.dirs=${cfg.package}/lib/endorsed
         '';
+        defaultText = literalExpression ''
+          '''
+            -Xms1200M
+            -Xmx1200M
+            -XX:MaxDirectMemorySize=2G
+            -XX:+UnlockDiagnosticVMOptions
+            -XX:+UnsyncloadClass
+            -XX:+LogVMOutput
+            -XX:LogFile=''${home}/nexus3/log/jvm.log
+            -XX:-OmitStackTraceInFastThrow
+            -Djava.net.preferIPv4Stack=true
+            -Dkaraf.home=''${package}
+            -Dkaraf.base=''${package}
+            -Dkaraf.etc=''${package}/etc/karaf
+            -Djava.util.logging.config.file=''${package}/etc/karaf/java.util.logging.properties
+            -Dkaraf.data=''${home}/nexus3
+            -Djava.io.tmpdir=''${home}/nexus3/tmp
+            -Dkaraf.startLocalConsole=false
+            -Djava.endorsed.dirs=''${package}/lib/endorsed
+          '''
+        '';
 
         description = ''
           Options for the JVM written to `nexus.jvmopts`.
diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix
index 4f6850ace214e..4512907f027b5 100644
--- a/nixos/modules/services/web-apps/node-red.nix
+++ b/nixos/modules/services/web-apps/node-red.nix
@@ -21,7 +21,7 @@ in
 
     package = mkOption {
       default = pkgs.nodePackages.node-red;
-      defaultText = "pkgs.nodePackages.node-red";
+      defaultText = literalExpression "pkgs.nodePackages.node-red";
       type = types.package;
       description = "Node-RED package to use.";
     };
@@ -46,7 +46,7 @@ in
     configFile = mkOption {
       type = types.path;
       default = "${cfg.package}/lib/node_modules/node-red/settings.js";
-      defaultText = "\${cfg.package}/lib/node_modules/node-red/settings.js";
+      defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/settings.js"'';
       description = ''
         Path to the JavaScript configuration file.
         See <link
@@ -102,7 +102,7 @@ in
       type = types.attrs;
       default = {};
       description = "List of settings.js overrides to pass via -D to Node-RED.";
-      example = literalExample ''
+      example = literalExpression ''
         {
           "logging.console.level" = "trace";
         }
@@ -114,6 +114,7 @@ in
     users.users = optionalAttrs (cfg.user == defaultUser) {
       ${defaultUser} = {
         isSystemUser = true;
+        group = defaultUser;
       };
     };
 
diff --git a/nixos/modules/services/web-apps/openwebrx.nix b/nixos/modules/services/web-apps/openwebrx.nix
new file mode 100644
index 0000000000000..51005cd1e4972
--- /dev/null
+++ b/nixos/modules/services/web-apps/openwebrx.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.openwebrx;
+in
+{
+  options.services.openwebrx = with lib; {
+    enable = mkEnableOption "OpenWebRX Web interface for Software-Defined Radios on http://localhost:8073";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.openwebrx;
+      description = "OpenWebRX package to use for the service";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.openwebrx = {
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        csdr
+        alsaUtils
+        netcat
+      ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/openwebrx";
+        Restart = "always";
+        DynamicUser = true;
+        # openwebrx uses /var/lib/openwebrx by default
+        StateDirectory = [ "openwebrx" ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
new file mode 100644
index 0000000000000..362a3358b7930
--- /dev/null
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -0,0 +1,447 @@
+{ lib, pkgs, config, ... }:
+
+let
+  cfg = config.services.peertube;
+
+  settingsFormat = pkgs.formats.json {};
+  configFile = settingsFormat.generate "production.json" cfg.settings;
+
+  env = {
+    NODE_CONFIG_DIR = "/var/lib/peertube/config";
+    NODE_ENV = "production";
+    NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+    NPM_CONFIG_PREFIX = cfg.package;
+    HOME = cfg.package;
+  };
+
+  systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ];
+
+  cfgService = {
+    # Proc filesystem
+    ProcSubset = "pid";
+    ProtectProc = "invisible";
+    # Access write directories
+    UMask = "0027";
+    # Capabilities
+    CapabilityBoundingSet = "";
+    # Security
+    NoNewPrivileges = true;
+    # Sandboxing
+    ProtectSystem = "strict";
+    ProtectHome = true;
+    PrivateTmp = true;
+    PrivateDevices = true;
+    PrivateUsers = true;
+    ProtectClock = true;
+    ProtectHostname = true;
+    ProtectKernelLogs = true;
+    ProtectKernelModules = true;
+    ProtectKernelTunables = true;
+    ProtectControlGroups = true;
+    RestrictNamespaces = true;
+    LockPersonality = true;
+    RestrictRealtime = true;
+    RestrictSUIDSGID = true;
+    RemoveIPC = true;
+    PrivateMounts = true;
+    # System Call Filtering
+    SystemCallArchitectures = "native";
+  };
+
+  envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") (
+    (lib.concatLists (lib.mapAttrsToList (name: value:
+      if value != null then [
+        "${name}=\"${toString value}\""
+      ] else []
+    ) env))));
+
+  peertubeEnv = pkgs.writeShellScriptBin "peertube-env" ''
+    set -a
+    source "${envFile}"
+    eval -- "\$@"
+  '';
+
+  peertubeCli = pkgs.writeShellScriptBin "peertube" ''
+    node ~/dist/server/tools/peertube.js $@
+  '';
+
+in {
+  options.services.peertube = {
+    enable = lib.mkEnableOption "Enable Peertube’s service";
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "peertube";
+      description = "User account under which Peertube runs.";
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "peertube";
+      description = "Group under which Peertube runs.";
+    };
+
+    localDomain = lib.mkOption {
+      type = lib.types.str;
+      example = "peertube.example.com";
+      description = "The domain serving your PeerTube instance.";
+    };
+
+    listenHttp = lib.mkOption {
+      type = lib.types.int;
+      default = 9000;
+      description = "listen port for HTTP server.";
+    };
+
+    listenWeb = lib.mkOption {
+      type = lib.types.int;
+      default = 9000;
+      description = "listen port for WEB server.";
+    };
+
+    enableWebHttps = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = "Enable or disable HTTPS protocol.";
+    };
+
+    dataDirs = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      example = [ "/opt/peertube/storage" "/var/cache/peertube" ];
+      description = "Allow access to custom data locations.";
+    };
+
+    serviceEnvironmentFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      example = "/run/keys/peertube/password-init-root";
+      description = ''
+        Set environment variables for the service. Mainly useful for setting the initial root password.
+        For example write to file:
+        PT_INITIAL_ROOT_PASSWORD=changeme
+      '';
+    };
+
+    settings = lib.mkOption {
+      type = settingsFormat.type;
+      example = lib.literalExpression ''
+        {
+          listen = {
+            hostname = "0.0.0.0";
+          };
+          log = {
+            level = "debug";
+          };
+          storage = {
+            tmp = "/opt/data/peertube/storage/tmp/";
+            logs = "/opt/data/peertube/storage/logs/";
+            cache = "/opt/data/peertube/storage/cache/";
+          };
+        }
+      '';
+      description = "Configuration for peertube.";
+    };
+
+    database = {
+      createLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Configure local PostgreSQL database server for PeerTube.";
+      };
+
+      host = lib.mkOption {
+        type = lib.types.str;
+        default = if cfg.database.createLocally then "/run/postgresql" else null;
+        example = "192.168.15.47";
+        description = "Database host address or unix socket.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.int;
+        default = 5432;
+        description = "Database host port.";
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "peertube";
+        description = "Database name.";
+      };
+
+      user = lib.mkOption {
+        type = lib.types.str;
+        default = "peertube";
+        description = "Database user.";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/peertube/password-posgressql-db";
+        description = "Password for PostgreSQL database.";
+      };
+    };
+
+    redis = {
+      createLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Configure local Redis server for PeerTube.";
+      };
+
+      host = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
+        description = "Redis host.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.nullOr lib.types.port;
+        default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 6379;
+        description = "Redis port.";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/peertube/password-redis-db";
+        description = "Password for redis database.";
+      };
+
+      enableUnixSocket = lib.mkOption {
+        type = lib.types.bool;
+        default = cfg.redis.createLocally;
+        description = "Use Unix socket.";
+      };
+    };
+
+    smtp = {
+      createLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Configure local Postfix SMTP server for PeerTube.";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/peertube/password-smtp";
+        description = "Password for smtp server.";
+      };
+    };
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.peertube;
+      description = "Peertube package to use.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile;
+          message = ''
+            <option>services.peertube.serviceEnvironmentFile</option> points to
+            a file in the Nix store. You should use a quoted absolute path to
+            prevent this.
+          '';
+      }
+      { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
+          message = ''
+            <option>services.peertube.redis.createLocally</option> and redis network connection (<option>services.peertube.redis.host</option> or <option>services.peertube.redis.port</option>) enabled. Disable either of them.
+        '';
+      }
+      { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null);
+          message = ''
+            <option>services.peertube.redis.host</option> and <option>services.peertube.redis.port</option> needs to be set if <option>services.peertube.redis.enableUnixSocket</option> is not enabled.
+        '';
+      }
+      { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile;
+          message = ''
+            <option>services.peertube.redis.passwordFile</option> points to
+            a file in the Nix store. You should use a quoted absolute path to
+            prevent this.
+          '';
+      }
+      { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile;
+          message = ''
+            <option>services.peertube.database.passwordFile</option> points to
+            a file in the Nix store. You should use a quoted absolute path to
+            prevent this.
+          '';
+      }
+      { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile;
+          message = ''
+            <option>services.peertube.smtp.passwordFile</option> points to
+            a file in the Nix store. You should use a quoted absolute path to
+            prevent this.
+          '';
+      }
+    ];
+
+    services.peertube.settings = lib.mkMerge [
+      {
+        listen = {
+          port = cfg.listenHttp;
+        };
+        webserver = {
+          https = (if cfg.enableWebHttps then true else false);
+          hostname = "${cfg.localDomain}";
+          port = cfg.listenWeb;
+        };
+        database = {
+          hostname = "${cfg.database.host}";
+          port = cfg.database.port;
+          name = "${cfg.database.name}";
+          username = "${cfg.database.user}";
+        };
+        redis = {
+          hostname = "${toString cfg.redis.host}";
+          port = (if cfg.redis.port == null then "" else cfg.redis.port);
+        };
+        storage = {
+          tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
+          avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
+          videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
+          streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
+          redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
+          logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
+          previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
+          thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
+          torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/";
+          captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
+          cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
+          plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
+          client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
+        };
+      }
+      (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis/redis.sock"; }; })
+    ];
+
+    systemd.tmpfiles.rules = [
+      "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
+      "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.peertube-init-db = lib.mkIf cfg.database.createLocally {
+      description = "Initialization database for PeerTube daemon";
+      after = [ "network.target" "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      script = let
+        psqlSetupCommands = pkgs.writeText "peertube-init.sql" ''
+          SELECT 'CREATE USER "${cfg.database.user}"' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${cfg.database.user}')\gexec
+          SELECT 'CREATE DATABASE "${cfg.database.name}" OWNER "${cfg.database.user}" TEMPLATE template0 ENCODING UTF8' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${cfg.database.name}')\gexec
+          \c '${cfg.database.name}'
+          CREATE EXTENSION IF NOT EXISTS pg_trgm;
+          CREATE EXTENSION IF NOT EXISTS unaccent;
+        '';
+      in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}";
+
+      serviceConfig = {
+        Type = "oneshot";
+        WorkingDirectory = cfg.package;
+        # User and group
+        User = "postgres";
+        Group = "postgres";
+        # Sandboxing
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        MemoryDenyWriteExecute = true;
+        # System Call Filtering
+        SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
+      } // cfgService;
+    };
+
+    systemd.services.peertube = {
+      description = "PeerTube daemon";
+      after = [ "network.target" ]
+        ++ lib.optionals cfg.redis.createLocally [ "redis.service" ]
+        ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = env;
+
+      path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn youtube-dl ];
+
+      script = ''
+        #!/bin/sh
+        umask 077
+        cat > /var/lib/peertube/config/local.yaml <<EOF
+        ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
+        database:
+          password: '$(cat ${cfg.database.passwordFile})'
+        ''}
+        ${lib.optionalString (cfg.redis.passwordFile != null) ''
+        redis:
+          auth: '$(cat ${cfg.redis.passwordFile})'
+        ''}
+        ${lib.optionalString (cfg.smtp.passwordFile != null) ''
+        smtp:
+          password: '$(cat ${cfg.smtp.passwordFile})'
+        ''}
+        EOF
+        ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
+        ln -sf ${configFile} /var/lib/peertube/config/production.json
+        npm start
+      '';
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+        RestartSec = 20;
+        TimeoutSec = 60;
+        WorkingDirectory = cfg.package;
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # State directory and mode
+        StateDirectory = "peertube";
+        StateDirectoryMode = "0750";
+        # Access write directories
+        ReadWritePaths = cfg.dataDirs;
+        # Environment
+        EnvironmentFile = cfg.serviceEnvironmentFile;
+        # Sandboxing
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+        MemoryDenyWriteExecute = false;
+        # System Call Filtering
+        SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ];
+      } // cfgService;
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+    };
+
+    services.redis = lib.mkMerge [
+      (lib.mkIf cfg.redis.createLocally {
+        enable = true;
+      })
+      (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
+        unixSocket = "/run/redis/redis.sock";
+        unixSocketPerm = 770;
+      })
+    ];
+
+    services.postfix = lib.mkIf cfg.smtp.createLocally {
+      enable = true;
+      hostname = lib.mkDefault "${cfg.localDomain}";
+    };
+
+    users.users = lib.mkMerge [
+      (lib.mkIf (cfg.user == "peertube") {
+        peertube = {
+          isSystemUser = true;
+          group = cfg.group;
+          home = cfg.package;
+        };
+      })
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ])
+      (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis" ];})
+    ];
+
+    users.groups = lib.optionalAttrs (cfg.group == "peertube") {
+      peertube = { };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index 838fd19ad2949..5642627d397df 100644
--- a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -21,7 +21,7 @@ in
 
       package = mkOption {
         default = pkgs.pgpkeyserver-lite;
-        defaultText = "pkgs.pgpkeyserver-lite";
+        defaultText = literalExpression "pkgs.pgpkeyserver-lite";
         type = types.package;
         description = "
           Which webgui derivation to use.
diff --git a/nixos/modules/services/web-apps/pict-rs.md b/nixos/modules/services/web-apps/pict-rs.md
new file mode 100644
index 0000000000000..4b622049909d2
--- /dev/null
+++ b/nixos/modules/services/web-apps/pict-rs.md
@@ -0,0 +1,88 @@
+# Pict-rs {#module-services-pict-rs}
+
+pict-rs is a  a simple image hosting service.
+
+## Quickstart {#module-services-pict-rs-quickstart}
+
+the minimum to start pict-rs is
+
+```nix
+services.pict-rs.enable = true;
+```
+
+this will start the http server on port 8080 by default.
+
+## Usage {#module-services-pict-rs-usage}
+
+pict-rs offers the following endpoints:
+- `POST /image` for uploading an image. Uploaded content must be valid multipart/form-data with an
+    image array located within the `images[]` key
+
+    This endpoint returns the following JSON structure on success with a 201 Created status
+    ```json
+    {
+        "files": [
+            {
+                "delete_token": "JFvFhqJA98",
+                "file": "lkWZDRvugm.jpg"
+            },
+            {
+                "delete_token": "kAYy9nk2WK",
+                "file": "8qFS0QooAn.jpg"
+            },
+            {
+                "delete_token": "OxRpM3sf0Y",
+                "file": "1hJaYfGE01.jpg"
+            }
+        ],
+        "msg": "ok"
+    }
+    ```
+- `GET /image/download?url=...` Download an image from a remote server, returning the same JSON
+    payload as the `POST` endpoint
+- `GET /image/original/{file}` for getting a full-resolution image. `file` here is the `file` key from the
+    `/image` endpoint's JSON
+- `GET /image/details/original/{file}` for getting the details of a full-resolution image.
+    The returned JSON is structured like so:
+    ```json
+    {
+        "width": 800,
+        "height": 537,
+        "content_type": "image/webp",
+        "created_at": [
+            2020,
+            345,
+            67376,
+            394363487
+        ]
+    }
+    ```
+- `GET /image/process.{ext}?src={file}&...` get a file with transformations applied.
+    existing transformations include
+    - `identity=true`: apply no changes
+    - `blur={float}`: apply a gaussian blur to the file
+    - `thumbnail={int}`: produce a thumbnail of the image fitting inside an `{int}` by `{int}`
+        square using raw pixel sampling
+    - `resize={int}`: produce a thumbnail of the image fitting inside an `{int}` by `{int}` square
+        using a Lanczos2 filter. This is slower than sampling but looks a bit better in some cases
+    - `crop={int-w}x{int-h}`: produce a cropped version of the image with an `{int-w}` by `{int-h}`
+        aspect ratio. The resulting crop will be centered on the image. Either the width or height
+        of the image will remain full-size, depending on the image's aspect ratio and the requested
+        aspect ratio. For example, a 1600x900 image cropped with a 1x1 aspect ratio will become 900x900. A
+        1600x1100 image cropped with a 16x9 aspect ratio will become 1600x900.
+
+    Supported `ext` file extensions include `png`, `jpg`, and `webp`
+
+    An example of usage could be
+    ```
+    GET /image/process.jpg?src=asdf.png&thumbnail=256&blur=3.0
+    ```
+    which would create a 256x256px JPEG thumbnail and blur it
+- `GET /image/details/process.{ext}?src={file}&...` for getting the details of a processed image.
+    The returned JSON is the same format as listed for the full-resolution details endpoint.
+- `DELETE /image/delete/{delete_token}/{file}` or `GET /image/delete/{delete_token}/{file}` to
+    delete a file, where `delete_token` and `file` are from the `/image` endpoint's JSON
+
+## Missing {#module-services-pict-rs-missing}
+
+- Configuring the secure-api-key is not included yet. The envisioned basic use case is consumption on localhost by other services without exposing the service to the internet.
diff --git a/nixos/modules/services/web-apps/pict-rs.nix b/nixos/modules/services/web-apps/pict-rs.nix
new file mode 100644
index 0000000000000..e1847fbd5314c
--- /dev/null
+++ b/nixos/modules/services/web-apps/pict-rs.nix
@@ -0,0 +1,50 @@
+{ lib, pkgs, config, ... }:
+with lib;
+let
+  cfg = config.services.pict-rs;
+in
+{
+  meta.maintainers = with maintainers; [ happysalada ];
+  # Don't edit the docbook xml directly, edit the md and generate it:
+  # `pandoc pict-rs.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > pict-rs.xml`
+  meta.doc = ./pict-rs.xml;
+
+  options.services.pict-rs = {
+    enable = mkEnableOption "pict-rs server";
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/pict-rs";
+      description = ''
+        The directory where to store the uploaded images.
+      '';
+    };
+    address = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        The IPv4 address to deploy the service to.
+      '';
+    };
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = ''
+        The port which to bind the service to.
+      '';
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    systemd.services.pict-rs = {
+      environment = {
+        PICTRS_PATH = cfg.dataDir;
+        PICTRS_ADDR = "${cfg.address}:${toString cfg.port}";
+      };
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "pict-rs";
+        ExecStart = "${pkgs.pict-rs}/bin/pict-rs";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/pict-rs.xml b/nixos/modules/services/web-apps/pict-rs.xml
new file mode 100644
index 0000000000000..bf129f5cc2ac2
--- /dev/null
+++ b/nixos/modules/services/web-apps/pict-rs.xml
@@ -0,0 +1,162 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-pict-rs">
+  <title>Pict-rs</title>
+  <para>
+    pict-rs is a a simple image hosting service.
+  </para>
+  <section xml:id="module-services-pict-rs-quickstart">
+    <title>Quickstart</title>
+    <para>
+      the minimum to start pict-rs is
+    </para>
+    <programlisting language="bash">
+services.pict-rs.enable = true;
+</programlisting>
+    <para>
+      this will start the http server on port 8080 by default.
+    </para>
+  </section>
+  <section xml:id="module-services-pict-rs-usage">
+    <title>Usage</title>
+    <para>
+      pict-rs offers the following endpoints: -
+      <literal>POST /image</literal> for uploading an image. Uploaded
+      content must be valid multipart/form-data with an image array
+      located within the <literal>images[]</literal> key
+    </para>
+    <programlisting>
+This endpoint returns the following JSON structure on success with a 201 Created status
+```json
+{
+    &quot;files&quot;: [
+        {
+            &quot;delete_token&quot;: &quot;JFvFhqJA98&quot;,
+            &quot;file&quot;: &quot;lkWZDRvugm.jpg&quot;
+        },
+        {
+            &quot;delete_token&quot;: &quot;kAYy9nk2WK&quot;,
+            &quot;file&quot;: &quot;8qFS0QooAn.jpg&quot;
+        },
+        {
+            &quot;delete_token&quot;: &quot;OxRpM3sf0Y&quot;,
+            &quot;file&quot;: &quot;1hJaYfGE01.jpg&quot;
+        }
+    ],
+    &quot;msg&quot;: &quot;ok&quot;
+}
+```
+</programlisting>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <literal>GET /image/download?url=...</literal> Download an
+          image from a remote server, returning the same JSON payload as
+          the <literal>POST</literal> endpoint
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>GET /image/original/{file}</literal> for getting a
+          full-resolution image. <literal>file</literal> here is the
+          <literal>file</literal> key from the <literal>/image</literal>
+          endpoint’s JSON
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>GET /image/details/original/{file}</literal> for
+          getting the details of a full-resolution image. The returned
+          JSON is structured like so:
+          <literal>json     {         &quot;width&quot;: 800,         &quot;height&quot;: 537,         &quot;content_type&quot;: &quot;image/webp&quot;,         &quot;created_at&quot;: [             2020,             345,             67376,             394363487         ]     }</literal>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>GET /image/process.{ext}?src={file}&amp;...</literal>
+          get a file with transformations applied. existing
+          transformations include
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              <literal>identity=true</literal>: apply no changes
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>blur={float}</literal>: apply a gaussian blur to
+              the file
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>thumbnail={int}</literal>: produce a thumbnail of
+              the image fitting inside an <literal>{int}</literal> by
+              <literal>{int}</literal> square using raw pixel sampling
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>resize={int}</literal>: produce a thumbnail of
+              the image fitting inside an <literal>{int}</literal> by
+              <literal>{int}</literal> square using a Lanczos2 filter.
+              This is slower than sampling but looks a bit better in
+              some cases
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>crop={int-w}x{int-h}</literal>: produce a cropped
+              version of the image with an <literal>{int-w}</literal> by
+              <literal>{int-h}</literal> aspect ratio. The resulting
+              crop will be centered on the image. Either the width or
+              height of the image will remain full-size, depending on
+              the image’s aspect ratio and the requested aspect ratio.
+              For example, a 1600x900 image cropped with a 1x1 aspect
+              ratio will become 900x900. A 1600x1100 image cropped with
+              a 16x9 aspect ratio will become 1600x900.
+            </para>
+          </listitem>
+        </itemizedlist>
+        <para>
+          Supported <literal>ext</literal> file extensions include
+          <literal>png</literal>, <literal>jpg</literal>, and
+          <literal>webp</literal>
+        </para>
+        <para>
+          An example of usage could be
+          <literal>GET /image/process.jpg?src=asdf.png&amp;thumbnail=256&amp;blur=3.0</literal>
+          which would create a 256x256px JPEG thumbnail and blur it
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>GET /image/details/process.{ext}?src={file}&amp;...</literal>
+          for getting the details of a processed image. The returned
+          JSON is the same format as listed for the full-resolution
+          details endpoint.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>DELETE /image/delete/{delete_token}/{file}</literal>
+          or <literal>GET /image/delete/{delete_token}/{file}</literal>
+          to delete a file, where <literal>delete_token</literal> and
+          <literal>file</literal> are from the <literal>/image</literal>
+          endpoint’s JSON
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="module-services-pict-rs-missing">
+    <title>Missing</title>
+    <itemizedlist spacing="compact">
+      <listitem>
+        <para>
+          Configuring the secure-api-key is not included yet. The
+          envisioned basic use case is consumption on localhost by other
+          services without exposing the service to the internet.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+</chapter>
diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix
index a39f594c274c4..f4bf43f56b98a 100644
--- a/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixos/modules/services/web-apps/plantuml-server.nix
@@ -16,6 +16,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.plantuml-server;
+        defaultText = literalExpression "pkgs.plantuml-server";
         description = "PlantUML server package to use";
       };
 
@@ -57,7 +58,8 @@ in
 
       graphvizPackage = mkOption {
         type = types.package;
-        default = pkgs.graphviz_2_32;
+        default = pkgs.graphviz;
+        defaultText = literalExpression "pkgs.graphviz";
         description = "Package containing the dot executable.";
       };
 
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index b56848b79d21c..b6c48186a1d32 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -5,23 +5,18 @@ with lib;
 let
   cfg = config.services.plausible;
 
-  # FIXME consider using LoadCredential as soon as it actually works.
-  envSecrets = ''
-    ADMIN_USER_PWD="$(<${cfg.adminUser.passwordFile})"
-    export ADMIN_USER_PWD # separate export to make `set -e` work
-
-    SECRET_KEY_BASE="$(<${cfg.server.secretKeybaseFile})"
-    export SECRET_KEY_BASE # separate export to make `set -e` work
-
-    ${optionalString (cfg.mail.smtp.passwordFile != null) ''
-      SMTP_USER_PWD="$(<${cfg.mail.smtp.passwordFile})"
-      export SMTP_USER_PWD # separate export to make `set -e` work
-    ''}
-  '';
 in {
   options.services.plausible = {
     enable = mkEnableOption "plausible";
 
+    releaseCookiePath = mkOption {
+      default = null;
+      type = with types; nullOr (either str path);
+      description = ''
+        The path to the file with release cookie. (used for remote connection to the running node).
+      '';
+    };
+
     adminUser = {
       name = mkOption {
         default = "admin";
@@ -184,13 +179,17 @@ in {
       enable = true;
     };
 
+    services.epmd.enable = true;
+
+    environment.systemPackages = [ pkgs.plausible ];
+
     systemd.services = mkMerge [
       {
         plausible = {
           inherit (pkgs.plausible.meta) description;
           documentation = [ "https://plausible.io/docs/self-hosting" ];
           wantedBy = [ "multi-user.target" ];
-          after = optional cfg.database.postgres.setup "plausible-postgres.service";
+          after = optionals cfg.database.postgres.setup [ "postgresql.service" "plausible-postgres.service" ];
           requires = optional cfg.database.clickhouse.setup "clickhouse.service"
             ++ optionals cfg.database.postgres.setup [
               "postgresql.service"
@@ -200,7 +199,7 @@ in {
           environment = {
             # NixOS specific option to avoid that it's trying to write into its store-path.
             # See also https://github.com/lau/tzdata#data-directory-and-releases
-            TZDATA_DIR = "/var/lib/plausible/elixir_tzdata";
+            STORAGE_DIR = "/var/lib/plausible/elixir_tzdata";
 
             # Configuration options from
             # https://plausible.io/docs/self-hosting-configuration
@@ -208,6 +207,8 @@ in {
             DISABLE_REGISTRATION = boolToString cfg.server.disableRegistration;
 
             RELEASE_TMP = "/var/lib/plausible/tmp";
+            # Home is needed to connect to the node with iex
+            HOME = "/var/lib/plausible";
 
             ADMIN_USER_NAME = cfg.adminUser.name;
             ADMIN_USER_EMAIL = cfg.adminUser.email;
@@ -231,28 +232,33 @@ in {
 
           path = [ pkgs.plausible ]
             ++ optional cfg.database.postgres.setup config.services.postgresql.package;
+          script = ''
+            export CONFIG_DIR=$CREDENTIALS_DIRECTORY
+
+            # setup
+            ${pkgs.plausible}/createdb.sh
+            ${pkgs.plausible}/migrate.sh
+            ${optionalString cfg.adminUser.activate ''
+              if ! ${pkgs.plausible}/init-admin.sh | grep 'already exists'; then
+                psql -d plausible <<< "UPDATE users SET email_verified=true;"
+              fi
+            ''}
+            ${optionalString (cfg.releaseCookiePath != null) ''
+              export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )"
+            ''}
+            plausible start
+          '';
 
           serviceConfig = {
             DynamicUser = true;
             PrivateTmp = true;
             WorkingDirectory = "/var/lib/plausible";
             StateDirectory = "plausible";
-            ExecStartPre = "@${pkgs.writeShellScript "plausible-setup" ''
-              set -eu -o pipefail
-              ${envSecrets}
-              ${pkgs.plausible}/createdb.sh
-              ${pkgs.plausible}/migrate.sh
-              ${optionalString cfg.adminUser.activate ''
-                if ! ${pkgs.plausible}/init-admin.sh | grep 'already exists'; then
-                  psql -d plausible <<< "UPDATE users SET email_verified=true;"
-                fi
-              ''}
-            ''} plausible-setup";
-            ExecStart = "@${pkgs.writeShellScript "plausible" ''
-              set -eu -o pipefail
-              ${envSecrets}
-              plausible start
-            ''} plausible";
+            LoadCredential = [
+              "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}"
+              "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}"
+            ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"]
+            ++ lib.optionals (cfg.releaseCookiePath != null) [ "RELEASE_COOKIE:${cfg.releaseCookiePath}"];
           };
         };
       }
@@ -260,20 +266,22 @@ in {
         # `plausible' requires the `citext'-extension.
         plausible-postgres = {
           after = [ "postgresql.service" ];
-          bindsTo = [ "postgresql.service" ];
-          requiredBy = [ "plausible.service" ];
           partOf = [ "plausible.service" ];
-          serviceConfig.Type = "oneshot";
-          unitConfig.ConditionPathExists = "!/var/lib/plausible/.db-setup";
-          script = ''
-            mkdir -p /var/lib/plausible/
+          serviceConfig = {
+            Type = "oneshot";
+            User = config.services.postgresql.superUser;
+            RemainAfterExit = true;
+          };
+          script = with cfg.database.postgres; ''
             PSQL() {
-              /run/wrappers/bin/sudo -Hu postgres ${config.services.postgresql.package}/bin/psql --port=5432 "$@"
+              ${config.services.postgresql.package}/bin/psql --port=5432 "$@"
             }
-            PSQL -tAc "CREATE ROLE plausible WITH LOGIN;"
-            PSQL -tAc "CREATE DATABASE plausible WITH OWNER plausible;"
-            PSQL -d plausible -tAc "CREATE EXTENSION IF NOT EXISTS citext;"
-            touch /var/lib/plausible/.db-setup
+            # check if the database already exists
+            if ! PSQL -lqt | ${pkgs.coreutils}/bin/cut -d \| -f 1 | ${pkgs.gnugrep}/bin/grep -qw ${dbname} ; then
+              PSQL -tAc "CREATE ROLE plausible WITH LOGIN;"
+              PSQL -tAc "CREATE DATABASE ${dbname} WITH OWNER plausible;"
+              PSQL -d ${dbname} -tAc "CREATE EXTENSION IF NOT EXISTS citext;"
+            fi
           '';
         };
       })
diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index 9d0a3f65253e9..fd97ab76a5f62 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -30,7 +30,6 @@ in
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/restya-board";
-        example = "/var/lib/restya-board";
         description = ''
           Data of the application.
         '';
@@ -39,7 +38,6 @@ in
       user = mkOption {
         type = types.str;
         default = "restya-board";
-        example = "restya-board";
         description = ''
           User account under which the web-application runs.
         '';
@@ -48,7 +46,6 @@ in
       group = mkOption {
         type = types.str;
         default = "nginx";
-        example = "nginx";
         description = ''
           Group account under which the web-application runs.
         '';
diff --git a/nixos/modules/services/web-apps/rss-bridge.nix b/nixos/modules/services/web-apps/rss-bridge.nix
index f1d5b7660f320..456ca00416feb 100644
--- a/nixos/modules/services/web-apps/rss-bridge.nix
+++ b/nixos/modules/services/web-apps/rss-bridge.nix
@@ -16,7 +16,6 @@ in
       user = mkOption {
         type = types.str;
         default = "nginx";
-        example = "nginx";
         description = ''
           User account under which both the service and the web-application run.
         '';
@@ -25,7 +24,6 @@ in
       group = mkOption {
         type = types.str;
         default = "nginx";
-        example = "nginx";
         description = ''
           Group under which the web-application run.
         '';
@@ -61,7 +59,7 @@ in
       whitelist = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = options.literalExample ''
+        example = options.literalExpression ''
           [
             "Facebook"
             "Instagram"
diff --git a/nixos/modules/services/web-apps/selfoss.nix b/nixos/modules/services/web-apps/selfoss.nix
index d5a660ebf2893..899976ac696c8 100644
--- a/nixos/modules/services/web-apps/selfoss.nix
+++ b/nixos/modules/services/web-apps/selfoss.nix
@@ -35,7 +35,6 @@ in
         user = mkOption {
           type = types.str;
           default = "nginx";
-          example = "nginx";
           description = ''
             User account under which both the service and the web-application run.
           '';
diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix
index a15bb9744a9c5..bb2fc684e83b1 100644
--- a/nixos/modules/services/web-apps/shiori.nix
+++ b/nixos/modules/services/web-apps/shiori.nix
@@ -11,7 +11,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.shiori;
-        defaultText = "pkgs.shiori";
+        defaultText = literalExpression "pkgs.shiori";
         description = "The Shiori package to use.";
       };
 
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index bc18c824f394c..08356cee1dfe7 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -126,7 +126,6 @@ let
       root = mkOption {
         type = types.path;
         default = "/var/lib/tt-rss";
-        example = "/var/lib/tt-rss";
         description = ''
           Root of the application.
         '';
@@ -135,7 +134,6 @@ let
       user = mkOption {
         type = types.str;
         default = "tt_rss";
-        example = "tt_rss";
         description = ''
           User account under which both the update daemon and the web-application run.
         '';
diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix
index b0b6eb6df17ef..7575e96ca815d 100644
--- a/nixos/modules/services/web-apps/vikunja.nix
+++ b/nixos/modules/services/web-apps/vikunja.nix
@@ -14,13 +14,13 @@ in {
     package-api = mkOption {
       default = pkgs.vikunja-api;
       type = types.package;
-      defaultText = "pkgs.vikunja-api";
+      defaultText = literalExpression "pkgs.vikunja-api";
       description = "vikunja-api derivation to use.";
     };
     package-frontend = mkOption {
       default = pkgs.vikunja-frontend;
       type = types.package;
-      defaultText = "pkgs.vikunja-frontend";
+      defaultText = literalExpression "pkgs.vikunja-frontend";
       description = "vikunja-frontend derivation to use.";
     };
     environmentFiles = mkOption {
@@ -34,7 +34,7 @@ in {
     setupNginx = mkOption {
       type = types.bool;
       default = config.services.nginx.enable;
-      defaultText = "config.services.nginx.enable";
+      defaultText = literalExpression "config.services.nginx.enable";
       description = ''
         Whether to setup NGINX.
         Further nginx configuration can be done by changing
diff --git a/nixos/modules/services/web-apps/whitebophir.nix b/nixos/modules/services/web-apps/whitebophir.nix
index b265296d5c1eb..f9db6fe379b0b 100644
--- a/nixos/modules/services/web-apps/whitebophir.nix
+++ b/nixos/modules/services/web-apps/whitebophir.nix
@@ -11,7 +11,7 @@ in {
 
       package = mkOption {
         default = pkgs.whitebophir;
-        defaultText = "pkgs.whitebophir";
+        defaultText = literalExpression "pkgs.whitebophir";
         type = types.package;
         description = "Whitebophir package to use.";
       };
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 6f1ef815bc46c..8ebb72296627d 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
+  inherit (lib) any attrValues concatMapStringsSep flatten literalExpression;
   inherit (lib) filterAttrs mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
 
   cfg = migrateOldAttrs config.services.wordpress;
@@ -87,6 +87,7 @@ let
         package = mkOption {
           type = types.package;
           default = pkgs.wordpress;
+          defaultText = literalExpression "pkgs.wordpress";
           description = "Which WordPress package to use.";
         };
 
@@ -106,23 +107,23 @@ let
             List of path(s) to respective plugin(s) which are copied from the 'plugins' directory.
             <note><para>These plugins need to be packaged before use, see example.</para></note>
           '';
-          example = ''
-            # Wordpress plugin 'embed-pdf-viewer' installation example
-            embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
-              name = "embed-pdf-viewer-plugin";
-              # Download the theme from the wordpress site
-              src = pkgs.fetchurl {
-                url = "https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip";
-                sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
+          example = literalExpression ''
+            let
+              # Wordpress plugin 'embed-pdf-viewer' installation example
+              embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
+                name = "embed-pdf-viewer-plugin";
+                # Download the theme from the wordpress site
+                src = pkgs.fetchurl {
+                  url = "https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip";
+                  sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
+                };
+                # We need unzip to build this package
+                nativeBuildInputs = [ pkgs.unzip ];
+                # Installing simply means copying all files to the output directory
+                installPhase = "mkdir -p $out; cp -R * $out/";
               };
-              # We need unzip to build this package
-              nativeBuildInputs = [ pkgs.unzip ];
-              # Installing simply means copying all files to the output directory
-              installPhase = "mkdir -p $out; cp -R * $out/";
-            };
-
-            And then pass this theme to the themes list like this:
-              plugins = [ embedPdfViewerPlugin ];
+            # And then pass this theme to the themes list like this:
+            in [ embedPdfViewerPlugin ]
           '';
         };
 
@@ -133,23 +134,23 @@ let
             List of path(s) to respective theme(s) which are copied from the 'theme' directory.
             <note><para>These themes need to be packaged before use, see example.</para></note>
           '';
-          example = ''
-            # Let's package the responsive theme
-            responsiveTheme = pkgs.stdenv.mkDerivation {
-              name = "responsive-theme";
-              # Download the theme from the wordpress site
-              src = pkgs.fetchurl {
-                url = "https://downloads.wordpress.org/theme/responsive.3.14.zip";
-                sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
+          example = literalExpression ''
+            let
+              # Let's package the responsive theme
+              responsiveTheme = pkgs.stdenv.mkDerivation {
+                name = "responsive-theme";
+                # Download the theme from the wordpress site
+                src = pkgs.fetchurl {
+                  url = "https://downloads.wordpress.org/theme/responsive.3.14.zip";
+                  sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
+                };
+                # We need unzip to build this package
+                nativeBuildInputs = [ pkgs.unzip ];
+                # Installing simply means copying all files to the output directory
+                installPhase = "mkdir -p $out; cp -R * $out/";
               };
-              # We need unzip to build this package
-              nativeBuildInputs = [ pkgs.unzip ];
-              # Installing simply means copying all files to the output directory
-              installPhase = "mkdir -p $out; cp -R * $out/";
-            };
-
-            And then pass this theme to the themes list like this:
-              themes = [ responsiveTheme ];
+            # And then pass this theme to the themes list like this:
+            in [ responsiveTheme ]
           '';
         };
 
@@ -204,7 +205,7 @@ let
           socket = mkOption {
             type = types.nullOr types.path;
             default = null;
-            defaultText = "/run/mysqld/mysqld.sock";
+            defaultText = literalExpression "/run/mysqld/mysqld.sock";
             description = "Path to the unix socket file to use for authentication.";
           };
 
@@ -217,7 +218,7 @@ let
 
         virtualHost = mkOption {
           type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-          example = literalExample ''
+          example = literalExpression ''
             {
               adminAddr = "webmaster@example.org";
               forceSSL = true;
@@ -278,7 +279,7 @@ in
         };
 
         options.webserver = mkOption {
-          type = types.enum [ "httpd" "nginx" ];
+          type = types.enum [ "httpd" "nginx" "caddy" ];
           default = "httpd";
           description = ''
             Whether to use apache2 or nginx for virtual host management.
@@ -458,5 +459,32 @@ in
     };
   })
 
+  (mkIf (cfg.webserver == "caddy") {
+    services.caddy = {
+      enable = true;
+      virtualHosts = mapAttrs' (hostName: cfg: (
+        nameValuePair "http://${hostName}" {
+          extraConfig = ''
+            root    * /${pkg hostName cfg}/share/wordpress
+            file_server
+
+            php_fastcgi unix/${config.services.phpfpm.pools."wordpress-${hostName}".socket}
+
+            @uploads {
+              path_regexp path /uploads\/(.*)\.php
+            }
+            rewrite @uploads /
+
+            @wp-admin {
+              path  not ^\/wp-admin/*
+            }
+            rewrite @wp-admin {path}/index.php?{query}
+          '';
+        }
+      )) eachSite;
+    };
+  })
+
+
   ]);
 }
diff --git a/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix
index b4d653d2d77e3..7a70ae6cd5238 100644
--- a/nixos/modules/services/web-apps/youtrack.nix
+++ b/nixos/modules/services/web-apps/youtrack.nix
@@ -46,7 +46,7 @@ in
         https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html
         for more information.
       '';
-      example = literalExample ''
+      example = literalExpression ''
         {
           "jetbrains.youtrack.overrideRootPassword" = "tortuga";
         }
@@ -60,7 +60,7 @@ in
       '';
       type = types.package;
       default = pkgs.youtrack;
-      defaultText = "pkgs.youtrack";
+      defaultText = literalExpression "pkgs.youtrack";
     };
 
     port = mkOption {
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index e94861a90b5a8..ff50b95254f90 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -3,7 +3,7 @@
 let
 
   inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) literalExample mapAttrs optionalString versionAtLeast;
+  inherit (lib) literalExpression mapAttrs optionalString versionAtLeast;
 
   cfg = config.services.zabbixWeb;
   fpm = config.services.phpfpm.pools.zabbix;
@@ -21,7 +21,8 @@ let
     $DB['PORT'] = '${toString cfg.database.port}';
     $DB['DATABASE'] = '${cfg.database.name}';
     $DB['USER'] = '${cfg.database.user}';
-    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "file_get_contents('${cfg.database.passwordFile}')" else "''"};
+    # NOTE: file_get_contents adds newline at the end of returned string
+    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")" else "''"};
     // Schema name. Used for IBM DB2 and PostgreSQL.
     $DB['SCHEMA'] = ''';
     $ZBX_SERVER = '${cfg.server.address}';
@@ -43,7 +44,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.zabbix.web;
-        defaultText = "zabbix.web";
+        defaultText = literalExpression "zabbix.web";
         description = "Which Zabbix package to use.";
       };
 
@@ -116,7 +117,7 @@ in
 
       virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
-        example = literalExample ''
+        example = literalExpression ''
           {
             hostName = "zabbix.example.org";
             adminAddr = "webmaster@example.org";
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index ceb1998709757..992a58875e435 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -407,7 +407,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.apacheHttpd;
-        defaultText = "pkgs.apacheHttpd";
+        defaultText = literalExpression "pkgs.apacheHttpd";
         description = ''
           Overridable attribute of the Apache HTTP Server package to use.
         '';
@@ -416,8 +416,8 @@ in
       configFile = mkOption {
         type = types.path;
         default = confFile;
-        defaultText = "confFile";
-        example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
+        defaultText = literalExpression "confFile";
+        example = literalExpression ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
         description = ''
           Override the configuration file used by Apache. By default,
           NixOS generates one automatically.
@@ -437,7 +437,7 @@ in
       extraModules = mkOption {
         type = types.listOf types.unspecified;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [
             "proxy_connect"
             { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
@@ -516,7 +516,14 @@ in
             documentRoot = "${pkg}/htdocs";
           };
         };
-        example = literalExample ''
+        defaultText = literalExpression ''
+          {
+            localhost = {
+              documentRoot = "''${package.out}/htdocs";
+            };
+          }
+        '';
+        example = literalExpression ''
           {
             "foo.example.com" = {
               forceSSL = true;
@@ -550,7 +557,7 @@ in
       phpPackage = mkOption {
         type = types.package;
         default = pkgs.php;
-        defaultText = "pkgs.php";
+        defaultText = literalExpression "pkgs.php";
         description = ''
           Overridable attribute of the PHP package to use.
         '';
diff --git a/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix b/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
index 3f732a5c9f336..8bb7e91ec9cdb 100644
--- a/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
@@ -1,6 +1,6 @@
 { config, lib, name, ... }:
 let
-  inherit (lib) literalExample mkOption nameValuePair types;
+  inherit (lib) literalExpression mkOption nameValuePair types;
 in
 {
   options = {
@@ -266,7 +266,7 @@ in
     locations = mkOption {
       type = with types; attrsOf (submodule (import ./location-options.nix));
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "/" = {
             proxyPass = "http://localhost:3000";
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index fd71020963434..cef27e2e59f3f 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -83,7 +83,7 @@ in
         inherit config lib;
       }));
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           "hydra.example.com" = {
             serverAliases = [ "www.hydra.example.com" ];
@@ -162,8 +162,7 @@ in
 
     package = mkOption {
       default = pkgs.caddy;
-      defaultText = "pkgs.caddy";
-      example = "pkgs.caddy";
+      defaultText = literalExpression "pkgs.caddy";
       type = types.package;
       description = ''
         Caddy package to use.
diff --git a/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
index 9f25dc34f3f00..8cd6d020940bf 100644
--- a/nixos/modules/services/web-servers/lighttpd/cgit.nix
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -41,11 +41,13 @@ in
 
     configText = mkOption {
       default = "";
-      example = ''
-        source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
-        about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
-        cache-size=1000
-        scan-path=/srv/git
+      example = literalExpression ''
+        '''
+          source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+          about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
+          cache-size=1000
+          scan-path=/srv/git
+        '''
       '';
       type = types.lines;
       description = ''
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
index 7a691aa789151..05e897c8cc946 100644
--- a/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -38,10 +38,13 @@ let
     "mod_rrdtool"
     "mod_accesslog"
     # Remaining list of modules, order assumed to be unimportant.
+    "mod_authn_dbi"
     "mod_authn_file"
     "mod_authn_gssapi"
     "mod_authn_ldap"
     "mod_authn_mysql"
+    "mod_authn_pam"
+    "mod_authn_sasl"
     "mod_cml"
     "mod_deflate"
     "mod_evasive"
@@ -132,6 +135,15 @@ in
         '';
       };
 
+      package = mkOption {
+        default = pkgs.lighttpd;
+        defaultText = "pkgs.lighttpd";
+        type = types.package;
+        description = ''
+          lighttpd package to use.
+        '';
+      };
+
       port = mkOption {
         default = 80;
         type = types.port;
@@ -240,7 +252,7 @@ in
       description = "Lighttpd Web Server";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}";
+      serviceConfig.ExecStart = "${cfg.package}/sbin/lighttpd -D -f ${configFile}";
       # SIGINT => graceful shutdown
       serviceConfig.KillSignal = "SIGINT";
     };
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
index 6b10afad49918..c345e3f2467bd 100644
--- a/nixos/modules/services/web-servers/minio.nix
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -87,7 +87,7 @@ in
 
     package = mkOption {
       default = pkgs.minio;
-      defaultText = "pkgs.minio";
+      defaultText = literalExpression "pkgs.minio";
       type = types.package;
       description = "Minio package to use.";
     };
diff --git a/nixos/modules/services/web-servers/molly-brown.nix b/nixos/modules/services/web-servers/molly-brown.nix
index 58db9b9beda06..0bd8b3316cb36 100644
--- a/nixos/modules/services/web-servers/molly-brown.nix
+++ b/nixos/modules/services/web-servers/molly-brown.nix
@@ -22,8 +22,8 @@ in {
 
     hostName = mkOption {
       type = types.str;
-      example = literalExample "config.networking.hostName";
       default = config.networking.hostName;
+      defaultText = literalExpression "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)
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 6682472fdb8e3..5717b86b3bea6 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -425,7 +425,7 @@ in
 
       package = mkOption {
         default = pkgs.nginxStable;
-        defaultText = "pkgs.nginxStable";
+        defaultText = literalExpression "pkgs.nginxStable";
         type = types.package;
         apply = p: p.override {
           modules = p.modules ++ cfg.additionalModules;
@@ -440,7 +440,7 @@ in
       additionalModules = mkOption {
         default = [];
         type = types.listOf (types.attrsOf types.anything);
-        example = literalExample "[ pkgs.nginxModules.brotli ]";
+        example = literalExpression "[ pkgs.nginxModules.brotli ]";
         description = ''
           Additional <link xlink:href="https://www.nginx.com/resources/wiki/modules/">third-party nginx modules</link>
           to install. Packaged modules are available in
@@ -674,7 +674,7 @@ in
             addresses = mkOption {
               type = types.listOf types.str;
               default = [];
-              example = literalExample ''[ "[::1]" "127.0.0.1:5353" ]'';
+              example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]'';
               description = "List of resolvers to use";
             };
             valid = mkOption {
@@ -738,7 +738,7 @@ in
           Defines a group of servers to use as proxy target.
         '';
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           "backend_server" = {
             servers = { "127.0.0.1:8000" = {}; };
             extraConfig = ''''
@@ -755,7 +755,7 @@ in
         default = {
           localhost = {};
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             "hydra.example.com" = {
               forceSSL = true;
@@ -889,14 +889,14 @@ in
         RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
         RestrictNamespaces = true;
         LockPersonality = true;
-        MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) cfg.package.modules);
+        MemoryDenyWriteExecute = !((builtins.any (mod: (mod.allowMemoryWriteExecute or false)) cfg.package.modules) || (cfg.package == pkgs.openresty));
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
         RemoveIPC = true;
         PrivateMounts = true;
         # System Call Filtering
         SystemCallArchitectures = "native";
-        SystemCallFilter = "~@cpu-emulation @debug @keyring @ipc @mount @obsolete @privileged @setuid";
+        SystemCallFilter = "~@cpu-emulation @debug @keyring @ipc @mount @obsolete @privileged @setuid @mincore";
       };
     };
 
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index d8c976f202fd1..56a5381e05c83 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -12,7 +12,7 @@ with lib;
     basicAuth = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           user = "password";
         };
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 94645e927f863..7ee041d372113 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -162,7 +162,7 @@ with lib;
     sslTrustedCertificate = mkOption {
       type = types.nullOr types.path;
       default = null;
-      example = "\${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+      example = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
       description = "Path to root SSL certificate for stapling and client certificates.";
     };
 
@@ -231,7 +231,7 @@ with lib;
     basicAuth = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           user = "password";
         };
@@ -261,7 +261,7 @@ with lib;
         inherit lib;
       }));
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "/" = {
             proxyPass = "http://localhost:3000";
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index 4d302299f5f04..87c68fa074a16 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -59,7 +59,7 @@ let
         phpPackage = mkOption {
           type = types.package;
           default = cfg.phpPackage;
-          defaultText = "config.services.phpfpm.phpPackage";
+          defaultText = literalExpression "config.services.phpfpm.phpPackage";
           description = ''
             The PHP package to use for running this PHP-FPM pool.
           '';
@@ -78,7 +78,7 @@ let
           description = ''
             Environment variables used for this PHP-FPM pool.
           '';
-          example = literalExample ''
+          example = literalExpression ''
             {
               HOSTNAME = "$HOSTNAME";
               TMP = "/tmp";
@@ -107,7 +107,7 @@ let
             for details. Note that settings names must be enclosed in quotes (e.g.
             <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
           '';
-          example = literalExample ''
+          example = literalExpression ''
             {
               "pm" = "dynamic";
               "pm.max_children" = 75;
@@ -179,7 +179,7 @@ in {
       phpPackage = mkOption {
         type = types.package;
         default = pkgs.php;
-        defaultText = "pkgs.php";
+        defaultText = literalExpression "pkgs.php";
         description = ''
           The PHP package to use for running the PHP-FPM service.
         '';
@@ -200,7 +200,7 @@ in {
       pools = mkOption {
         type = types.attrsOf (types.submodule poolOpts);
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
          {
            mypool = {
              user = "php";
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index 13fe98402c60e..f9446fe125a34 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -24,8 +24,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.tomcat85;
-        defaultText = "pkgs.tomcat85";
-        example = lib.literalExample "pkgs.tomcat9";
+        defaultText = literalExpression "pkgs.tomcat85";
+        example = lib.literalExpression "pkgs.tomcat9";
         description = ''
           Which tomcat package to use.
         '';
@@ -127,7 +127,7 @@ in
       webapps = mkOption {
         type = types.listOf types.path;
         default = [ tomcat.webapps ];
-        defaultText = "[ pkgs.tomcat85.webapps ]";
+        defaultText = literalExpression "[ pkgs.tomcat85.webapps ]";
         description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
       };
 
@@ -166,7 +166,7 @@ in
       jdk = mkOption {
         type = types.package;
         default = pkgs.jdk;
-        defaultText = "pkgs.jdk";
+        defaultText = literalExpression "pkgs.jdk";
         description = "Which JDK to use.";
       };
 
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index 3d29199dd4549..eb7fd0995de08 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -54,7 +54,7 @@ in {
 
     staticConfigFile = mkOption {
       default = null;
-      example = literalExample "/path/to/static_config.toml";
+      example = literalExpression "/path/to/static_config.toml";
       type = types.nullOr types.path;
       description = ''
         Path to traefik's static configuration to use.
@@ -78,7 +78,7 @@ in {
 
     dynamicConfigFile = mkOption {
       default = null;
-      example = literalExample "/path/to/dynamic_config.toml";
+      example = literalExpression "/path/to/dynamic_config.toml";
       type = types.nullOr types.path;
       description = ''
         Path to traefik's dynamic configuration to use.
@@ -123,7 +123,7 @@ in {
 
     package = mkOption {
       default = pkgs.traefik;
-      defaultText = "pkgs.traefik";
+      defaultText = literalExpression "pkgs.traefik";
       type = types.package;
       description = "Traefik package to use.";
     };
diff --git a/nixos/modules/services/web-servers/trafficserver.nix b/nixos/modules/services/web-servers/trafficserver/default.nix
index db0e2ac0bd05a..b52087fa038c1 100644
--- a/nixos/modules/services/web-servers/trafficserver.nix
+++ b/nixos/modules/services/web-servers/trafficserver/default.nix
@@ -8,21 +8,9 @@ let
   group = config.users.groups.trafficserver.name;
 
   getManualUrl = name: "https://docs.trafficserver.apache.org/en/latest/admin-guide/files/${name}.en.html";
-  getConfPath = name: "${pkgs.trafficserver}/etc/trafficserver/${name}";
 
   yaml = pkgs.formats.yaml { };
 
-  fromYAML = f:
-    let
-      jsonFile = pkgs.runCommand "in.json"
-        {
-          nativeBuildInputs = [ pkgs.remarshal ];
-        } ''
-        yaml2json < "${f}" > "$out"
-      '';
-    in
-    builtins.fromJSON (builtins.readFile jsonFile);
-
   mkYamlConf = name: cfg:
     if cfg != null then {
       "trafficserver/${name}.yaml".source = yaml.generate "${name}.yaml" cfg;
@@ -73,16 +61,18 @@ in
 
     ipAllow = mkOption {
       type = types.nullOr yaml.type;
-      default = fromYAML (getConfPath "ip_allow.yaml");
-      defaultText = "upstream defaults";
-      example = literalExample {
-        ip_allow = [{
-          apply = "in";
-          ip_addrs = "127.0.0.1";
-          action = "allow";
-          methods = "ALL";
-        }];
-      };
+      default = lib.importJSON ./ip_allow.json;
+      defaultText = literalDocBook "upstream defaults";
+      example = literalExpression ''
+        {
+          ip_allow = [{
+            apply = "in";
+            ip_addrs = "127.0.0.1";
+            action = "allow";
+            methods = "ALL";
+          }];
+        }
+      '';
       description = ''
         Control client access to Traffic Server and Traffic Server connections
         to upstream servers.
@@ -94,9 +84,9 @@ in
 
     logging = mkOption {
       type = types.nullOr yaml.type;
-      default = fromYAML (getConfPath "logging.yaml");
-      defaultText = "upstream defaults";
-      example = literalExample { };
+      default = lib.importJSON ./logging.json;
+      defaultText = literalDocBook "upstream defaults";
+      example = { };
       description = ''
         Configure logs.
 
@@ -157,7 +147,7 @@ in
         in
         valueType;
       default = { };
-      example = literalExample { proxy.config.proxy_name = "my_server"; };
+      example = { proxy.config.proxy_name = "my_server"; };
       description = ''
         List of configurable variables used by Traffic Server.
 
@@ -209,12 +199,14 @@ in
     sni = mkOption {
       type = types.nullOr yaml.type;
       default = null;
-      example = literalExample {
-        sni = [{
-          fqdn = "no-http2.example.com";
-          https = "off";
-        }];
-      };
+      example = literalExpression ''
+        {
+          sni = [{
+            fqdn = "no-http2.example.com";
+            https = "off";
+          }];
+        }
+      '';
       description = ''
         Configure aspects of TLS connection handling for both inbound and
         outbound connections.
diff --git a/nixos/modules/services/web-servers/trafficserver/ip_allow.json b/nixos/modules/services/web-servers/trafficserver/ip_allow.json
new file mode 100644
index 0000000000000..fc2db8037286c
--- /dev/null
+++ b/nixos/modules/services/web-servers/trafficserver/ip_allow.json
@@ -0,0 +1,36 @@
+{
+  "ip_allow": [
+    {
+      "apply": "in",
+      "ip_addrs": "127.0.0.1",
+      "action": "allow",
+      "methods": "ALL"
+    },
+    {
+      "apply": "in",
+      "ip_addrs": "::1",
+      "action": "allow",
+      "methods": "ALL"
+    },
+    {
+      "apply": "in",
+      "ip_addrs": "0/0",
+      "action": "deny",
+      "methods": [
+        "PURGE",
+        "PUSH",
+        "DELETE"
+      ]
+    },
+    {
+      "apply": "in",
+      "ip_addrs": "::/0",
+      "action": "deny",
+      "methods": [
+        "PURGE",
+        "PUSH",
+        "DELETE"
+      ]
+    }
+  ]
+}
diff --git a/nixos/modules/services/web-servers/trafficserver/logging.json b/nixos/modules/services/web-servers/trafficserver/logging.json
new file mode 100644
index 0000000000000..81e7ba0186c6e
--- /dev/null
+++ b/nixos/modules/services/web-servers/trafficserver/logging.json
@@ -0,0 +1,37 @@
+{
+  "logging": {
+    "formats": [
+      {
+        "name": "welf",
+        "format": "id=firewall time=\"%<cqtd> %<cqtt>\" fw=%<phn> pri=6 proto=%<cqus> duration=%<ttmsf> sent=%<psql> rcvd=%<cqhl> src=%<chi> dst=%<shi> dstname=%<shn> user=%<caun> op=%<cqhm> arg=\"%<cqup>\" result=%<pssc> ref=\"%<{Referer}cqh>\" agent=\"%<{user-agent}cqh>\" cache=%<crc>"
+      },
+      {
+        "name": "squid_seconds_only_timestamp",
+        "format": "%<cqts> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>"
+      },
+      {
+        "name": "squid",
+        "format": "%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>"
+      },
+      {
+        "name": "common",
+        "format": "%<chi> - %<caun> [%<cqtn>] \"%<cqtx>\" %<pssc> %<pscl>"
+      },
+      {
+        "name": "extended",
+        "format": "%<chi> - %<caun> [%<cqtn>] \"%<cqtx>\" %<pssc> %<pscl> %<sssc> %<sscl> %<cqcl> %<pqcl> %<cqhl> %<pshl> %<pqhl> %<sshl> %<tts>"
+      },
+      {
+        "name": "extended2",
+        "format": "%<chi> - %<caun> [%<cqtn>] \"%<cqtx>\" %<pssc> %<pscl> %<sssc> %<sscl> %<cqcl> %<pqcl> %<cqhl> %<pshl> %<pqhl> %<sshl> %<tts> %<phr> %<cfsc> %<pfsc> %<crc>"
+      }
+    ],
+    "logs": [
+      {
+        "filename": "squid",
+        "format": "squid",
+        "mode": "binary"
+      }
+    ]
+  }
+}
diff --git a/nixos/modules/services/web-servers/ttyd.nix b/nixos/modules/services/web-servers/ttyd.nix
index 68d55ee6ffd2d..431509f7fd560 100644
--- a/nixos/modules/services/web-servers/ttyd.nix
+++ b/nixos/modules/services/web-servers/ttyd.nix
@@ -78,7 +78,7 @@ in
       clientOptions = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        example = literalExample ''{
+        example = literalExpression ''{
           fontSize = "16";
           fontFamily = "Fira Code";
 
diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index 2a264bf2e9a6f..b2eecdbb53eec 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -14,7 +14,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.unit;
-        defaultText = "pkgs.unit";
+        defaultText = literalExpression "pkgs.unit";
         description = "Unit package to use.";
       };
       user = mkOption {
@@ -45,7 +45,7 @@ in {
             "applications": {}
           }
         '';
-        example = literalExample ''
+        example = ''
           {
             "listeners": {
               "*:8300": {
diff --git a/nixos/modules/services/web-servers/uwsgi.nix b/nixos/modules/services/web-servers/uwsgi.nix
index 2dfc39c847aa8..ac435951310eb 100644
--- a/nixos/modules/services/web-servers/uwsgi.nix
+++ b/nixos/modules/services/web-servers/uwsgi.nix
@@ -114,7 +114,7 @@ in {
         default = {
           type = "normal";
         };
-        example = literalExample ''
+        example = literalExpression ''
           {
             type = "emperor";
             vassals = {
@@ -163,7 +163,7 @@ in {
         type = types.listOf types.str;
         apply = caps: caps ++ optionals isEmperor imperialPowers;
         default = [ ];
-        example = literalExample ''
+        example = literalExpression ''
           [
             "CAP_NET_BIND_SERVICE" # bind on ports <1024
             "CAP_NET_RAW"          # open raw sockets
diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix
index 01fe3d12917a7..0ebf58eb9f616 100644
--- a/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixos/modules/services/web-servers/varnish/default.nix
@@ -13,10 +13,12 @@ in
     services.varnish = {
       enable = mkEnableOption "Varnish Server";
 
+      enableConfigCheck = mkEnableOption "checking the config during build time" // { default = true; };
+
       package = mkOption {
         type = types.package;
         default = pkgs.varnish;
-        defaultText = "pkgs.varnish";
+        defaultText = literalExpression "pkgs.varnish";
         description = ''
           The package to use
         '';
@@ -48,7 +50,7 @@ in
       extraModules = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.varnishPackages.geoip ]";
+        example = literalExpression "[ pkgs.varnishPackages.geoip ]";
         description = "
           Varnish modules (except 'std').
         ";
@@ -96,11 +98,10 @@ in
     environment.systemPackages = [ cfg.package ];
 
     # check .vcl syntax at compile time (e.g. before nixops deployment)
-    system.extraDependencies = [
-      (pkgs.stdenv.mkDerivation {
-        name = "check-varnish-syntax";
-        buildCommand = "${cfg.package}/sbin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)";
-      })
+    system.extraDependencies = mkIf cfg.enableConfigCheck [
+      (pkgs.runCommand "check-varnish-syntax" {} ''
+        ${cfg.package}/bin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)
+      '')
     ];
 
     users.users.varnish = {
diff --git a/nixos/modules/services/web-servers/zope2.nix b/nixos/modules/services/web-servers/zope2.nix
index 3abd506827c09..922109160228a 100644
--- a/nixos/modules/services/web-servers/zope2.nix
+++ b/nixos/modules/services/web-servers/zope2.nix
@@ -75,7 +75,7 @@ in
     services.zope2.instances = mkOption {
       default = {};
       type = with types; attrsOf (submodule zope2Opts);
-      example = literalExample ''
+      example = literalExpression ''
         {
           plone01 = {
             http_address = "127.0.0.1:8080";
@@ -103,7 +103,11 @@ in
 
   config = mkIf (cfg.instances != {}) {
 
-    users.users.zope2.uid = config.ids.uids.zope2;
+    users.users.zope2 = {
+      isSystemUser = true;
+      group = "zope2";
+    };
+    users.groups.zope2 = {};
 
     systemd.services =
       let
diff --git a/nixos/modules/services/x11/clight.nix b/nixos/modules/services/x11/clight.nix
index 873f425fb8be4..d994a658cbaa5 100644
--- a/nixos/modules/services/x11/clight.nix
+++ b/nixos/modules/services/x11/clight.nix
@@ -71,6 +71,14 @@ in {
   };
 
   config = mkIf cfg.enable {
+    assertions = let
+      inRange = v: l: r: v >= l && v <= r;
+    in [
+      { assertion = config.location.provider == "manual" ->
+          inRange config.location.latitude (-90) 90 && inRange config.location.longitude (-180) 180;
+        message = "You must specify a valid latitude and longitude if manually providing location"; }
+    ];
+
     boot.kernelModules = [ "i2c_dev" ];
     environment.systemPackages = with pkgs; [ clight clightd ];
     services.dbus.packages = with pkgs; [ clight clightd ];
diff --git a/nixos/modules/services/x11/desktop-managers/cde.nix b/nixos/modules/services/x11/desktop-managers/cde.nix
index 3f1575a0ca637..6c7105729cfd4 100644
--- a/nixos/modules/services/x11/desktop-managers/cde.nix
+++ b/nixos/modules/services/x11/desktop-managers/cde.nix
@@ -14,7 +14,7 @@ in {
       default = with pkgs.xorg; [
         xclock bitmap xlsfonts xfd xrefresh xload xwininfo xdpyinfo xwd xwud
       ];
-      example = literalExample ''
+      defaultText = literalExpression ''
         with pkgs.xorg; [
           xclock bitmap xlsfonts xfd xrefresh xload xwininfo xdpyinfo xwd xwud
         ]
@@ -49,9 +49,10 @@ in {
     users.groups.mail = {};
     security.wrappers = {
       dtmail = {
-        source = "${pkgs.cdesktopenv}/bin/dtmail";
-        group = "mail";
         setgid = true;
+        owner = "root";
+        group = "mail";
+        source = "${pkgs.cdesktopenv}/bin/dtmail";
       };
     };
 
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index d201c1a5334b4..a0a5873f72fec 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -26,7 +26,7 @@ in
       sessionPath = mkOption {
         default = [];
         type = types.listOf types.package;
-        example = literalExample "[ pkgs.gnome.gpaste ]";
+        example = literalExpression "[ pkgs.gnome.gpaste ]";
         description = ''
           Additional list of packages to be added to the session search path.
           Useful for GSettings-conditional autostart.
@@ -50,7 +50,7 @@ in
 
     environment.cinnamon.excludePackages = mkOption {
       default = [];
-      example = literalExample "[ pkgs.cinnamon.blueberry ]";
+      example = literalExpression "[ pkgs.cinnamon.blueberry ]";
       type = types.listOf types.package;
       description = "Which packages cinnamon should exclude from the default environment";
     };
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 3a7ab64510b5c..e3d876e82fddb 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -65,9 +65,24 @@ in
 
     # Wrappers for programs installed by enlightenment that should be setuid
     security.wrappers = {
-      enlightenment_ckpasswd.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd";
-      enlightenment_sys.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys";
-      enlightenment_system.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system";
+      enlightenment_ckpasswd =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd";
+        };
+      enlightenment_sys =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys";
+        };
+      enlightenment_system =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system";
+        };
     };
 
     environment.etc."X11/xkb".source = xcfg.xkbDir;
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index 4bc42525906c6..efc9bd39b366a 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -186,7 +186,7 @@ in
       sessionPath = mkOption {
         default = [];
         type = types.listOf types.package;
-        example = literalExample "[ pkgs.gnome.gpaste ]";
+        example = literalExpression "[ pkgs.gnome.gpaste ]";
         description = ''
           Additional list of packages to be added to the session search path.
           Useful for GNOME Shell extensions or GSettings-conditional autostart.
@@ -200,9 +200,11 @@ in
         internal = true; # this is messy
         default = defaultFavoriteAppsOverride;
         type = types.lines;
-        example = literalExample ''
-          [org.gnome.shell]
-          favorite-apps=[ 'firefox.desktop', 'org.gnome.Calendar.desktop' ]
+        example = literalExpression ''
+          '''
+            [org.gnome.shell]
+            favorite-apps=[ 'firefox.desktop', 'org.gnome.Calendar.desktop' ]
+          '''
         '';
         description = "List of desktop files to put as favorite apps into gnome-shell. These need to be installed somehow globally.";
       };
@@ -242,13 +244,13 @@ in
               wmCommand = mkOption {
                 type = types.str;
                 description = "The executable of the window manager to use.";
-                example = "\${pkgs.haskellPackages.xmonad}/bin/xmonad";
+                example = literalExpression ''"''${pkgs.haskellPackages.xmonad}/bin/xmonad"'';
               };
 
               enableGnomePanel = mkOption {
                 type = types.bool;
                 default = true;
-                example = "false";
+                example = false;
                 description = "Whether to enable the GNOME panel in this session.";
               };
             };
@@ -259,20 +261,20 @@ in
 
         panelModulePackages = mkOption {
           default = [ pkgs.gnome.gnome-applets ];
+          defaultText = literalExpression "[ pkgs.gnome.gnome-applets ]";
           type = types.listOf types.path;
           description = ''
             Packages containing modules that should be made available to <literal>gnome-panel</literal> (usually for applets).
 
             If you're packaging something to use here, please install the modules in <literal>$out/lib/gnome-panel/modules</literal>.
           '';
-          example = literalExample "[ pkgs.gnome.gnome-applets ]";
         };
       };
     };
 
     environment.gnome.excludePackages = mkOption {
       default = [];
-      example = literalExample "[ pkgs.gnome.totem ]";
+      example = literalExpression "[ pkgs.gnome.totem ]";
       type = types.listOf types.package;
       description = "Which packages gnome should exclude from the default environment";
     };
@@ -370,7 +372,13 @@ in
       services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
 
       xdg.portal.enable = true;
-      xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+      xdg.portal.extraPortals = [
+        pkgs.xdg-desktop-portal-gnome
+        (pkgs.xdg-desktop-portal-gtk.override {
+          # Do not build portals that we already have.
+          buildPortalsInGnome = false;
+        })
+      ];
 
       # Harmonize Qt5 application style and also make them use the portal for file chooser dialog.
       qt5 = {
@@ -445,7 +453,7 @@ in
         cantarell-fonts
         dejavu_fonts
         source-code-pro # Default monospace font in 3.32
-        source-sans-pro
+        source-sans
       ];
 
       # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-38/elements/core/meta-gnome-core-shell.bst
@@ -476,6 +484,8 @@ in
     (mkIf serviceCfg.experimental-features.realtime-scheduling {
       security.wrappers.".gnome-shell-wrapped" = {
         source = "${pkgs.gnome.gnome-shell}/bin/.gnome-shell-wrapped";
+        owner = "root";
+        group = "root";
         capabilities = "cap_sys_nice=ep";
       };
 
diff --git a/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixos/modules/services/x11/desktop-managers/kodi.nix
index af303d6fb2797..b853c94d6fd47 100644
--- a/nixos/modules/services/x11/desktop-managers/kodi.nix
+++ b/nixos/modules/services/x11/desktop-managers/kodi.nix
@@ -18,8 +18,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.kodi;
-        defaultText = "pkgs.kodi";
-        example = "pkgs.kodi.withPackages (p: with p; [ jellyfin pvr-iptvsimple vfs-sftp ])";
+        defaultText = literalExpression "pkgs.kodi";
+        example = literalExpression "pkgs.kodi.withPackages (p: with p; [ jellyfin pvr-iptvsimple vfs-sftp ])";
         description = ''
           Package that should be used for Kodi.
         '';
diff --git a/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixos/modules/services/x11/desktop-managers/lumina.nix
index 419f5055d8be9..1ab61953e7355 100644
--- a/nixos/modules/services/x11/desktop-managers/lumina.nix
+++ b/nixos/modules/services/x11/desktop-managers/lumina.nix
@@ -38,5 +38,11 @@ in
       "/share"
     ];
 
+    security.wrappers.lumina-checkpass-wrapped = {
+      source = "${pkgs.lumina.lumina}/bin/lumina-checkpass";
+      owner = "root";
+      group = "root";
+    };
+
   };
 }
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 71dfad5c7ca02..720985ba0d94e 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -19,7 +19,7 @@ in
 
     environment.lxqt.excludePackages = mkOption {
       default = [];
-      example = literalExample "[ pkgs.lxqt.qterminal ]";
+      example = literalExpression "[ pkgs.lxqt.qterminal ]";
       type = types.listOf types.package;
       description = "Which LXQt packages to exclude from the default environment";
     };
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index 19ab9edb7324f..f8f47a0614524 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -35,7 +35,7 @@ in
 
     environment.mate.excludePackages = mkOption {
       default = [];
-      example = literalExample "[ pkgs.mate.mate-terminal pkgs.mate.pluma ]";
+      example = literalExpression "[ pkgs.mate.mate-terminal pkgs.mate.pluma ]";
       type = types.listOf types.package;
       description = "Which MATE packages to exclude from the default environment";
     };
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index e492073b80ffd..5a41f96497f2b 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -18,7 +18,7 @@ in
 
   meta = {
     doc = ./pantheon.xml;
-    maintainers = pkgs.pantheon.maintainers;
+    maintainers = teams.pantheon.members;
   };
 
   options = {
@@ -43,7 +43,7 @@ in
       sessionPath = mkOption {
         default = [];
         type = types.listOf types.package;
-        example = literalExample "[ pkgs.gnome.gpaste ]";
+        example = literalExpression "[ pkgs.gnome.gpaste ]";
         description = ''
           Additional list of packages to be added to the session search path.
           Useful for GSettings-conditional autostart.
@@ -86,7 +86,7 @@ in
 
     environment.pantheon.excludePackages = mkOption {
       default = [];
-      example = literalExample "[ pkgs.pantheon.elementary-camera ]";
+      example = literalExpression "[ pkgs.pantheon.elementary-camera ]";
       type = types.listOf types.package;
       description = "Which packages pantheon should exclude from the default environment";
     };
@@ -134,6 +134,9 @@ in
       services.accounts-daemon.enable = true;
       services.bamf.enable = true;
       services.colord.enable = mkDefault true;
+      services.fwupd.enable = mkDefault true;
+      services.touchegg.enable = mkDefault true;
+      services.touchegg.package = pkgs.pantheon.touchegg;
       services.tumbler.enable = mkDefault true;
       services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
       services.dbus.packages = with pkgs.pantheon; [
@@ -162,12 +165,11 @@ in
         isAllowed = true;
         isSystem = true;
       };
-      # Use gnome-settings-daemon fork
       services.udev.packages = [
-        pkgs.pantheon.elementary-settings-daemon
+        pkgs.gnome.gnome-settings-daemon338
       ];
       systemd.packages = [
-        pkgs.pantheon.elementary-settings-daemon
+        pkgs.gnome.gnome-settings-daemon338
       ];
       programs.dconf.enable = true;
       networking.networkmanager.enable = mkDefault true;
@@ -180,7 +182,6 @@ in
         gnome.adwaita-icon-theme
         gtk3.out
         hicolor-icon-theme
-        lightlocker
         onboard
         qgnomeplatform
         shared-mime-info
@@ -208,25 +209,30 @@ in
 
         # Services
         elementary-capnet-assist
-        elementary-dpms-helper
         elementary-notifications
         elementary-settings-daemon
         pantheon-agent-geoclue2
         pantheon-agent-polkit
       ]) ++ (gnome.removePackagesByName [
-        gnome.geary
-        gnome.epiphany
         gnome.gnome-font-viewer
+        gnome.gnome-settings-daemon338
       ] config.environment.pantheon.excludePackages);
 
       programs.evince.enable = mkDefault true;
+      programs.evince.package = pkgs.pantheon.evince;
       programs.file-roller.enable = mkDefault true;
+      programs.file-roller.package = pkgs.pantheon.file-roller;
 
       # Settings from elementary-default-settings
       environment.sessionVariables.GTK_CSD = "1";
-      environment.sessionVariables.GTK3_MODULES = [ "pantheon-filechooser-module" ];
       environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini";
 
+      xdg.portal.extraPortals = with pkgs; [
+        pantheon.elementary-files
+        pantheon.elementary-settings-daemon
+        xdg-desktop-portal-pantheon
+      ];
+
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
 
@@ -254,13 +260,15 @@ in
 
       # Default Fonts
       fonts.fonts = with pkgs; [
+        inter
+        open-dyslexic
         open-sans
         roboto-mono
       ];
 
       fonts.fontconfig.defaultFonts = {
         monospace = [ "Roboto Mono" ];
-        sansSerif = [ "Open Sans" ];
+        sansSerif = [ "Inter" ];
       };
     })
 
@@ -271,14 +279,17 @@ in
         elementary-camera
         elementary-code
         elementary-files
+        elementary-mail
         elementary-music
         elementary-photos
-        elementary-screenshot-tool
+        elementary-screenshot
+        elementary-tasks
         elementary-terminal
         elementary-videos
+        epiphany
       ] config.environment.pantheon.excludePackages);
 
-      # needed by screenshot-tool
+      # needed by screenshot
       fonts.fonts = [
         pkgs.pantheon.elementary-redacted-script
       ];
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml
index 7905ceebd9aaa..64933349e7988 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.xml
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.xml
@@ -22,7 +22,7 @@
 <programlisting>
 <xref linkend="opt-services.pantheon.apps.enable"/> = false;
 </programlisting>
-   You can also use <xref linkend="opt-environment.pantheon.excludePackages"/> to remove any other app (like <package>geary</package>).
+   You can also use <xref linkend="opt-environment.pantheon.excludePackages"/> to remove any other app (like <package>elementary-mail</package>).
   </para>
  </section>
  <section xml:id="sec-pantheon-wingpanel-switchboard">
@@ -105,7 +105,7 @@ switchboard-with-plugs.override {
     </term>
     <listitem>
      <para>
-      AppCenter has been available since 20.03, but it is of little use. This is because there is no functioning PackageKit backend for Nix 2.0. In the near future you will be able to install Flatpak applications from AppCenter on NixOS. See this <link xlink:href="https://github.com/NixOS/nixpkgs/issues/70214">issue</link>.
+      AppCenter has been available since 20.03, but it is of little use. This is because there is no functioning PackageKit backend for Nix 2.0. The Flatpak backend will not work before <link xlink:href="https://github.com/elementary/appcenter/issues/1076">flag for Flatpak-only</link> is provided. See this <link xlink:href="https://github.com/NixOS/nixpkgs/issues/70214">issue</link>.
      </para>
     </listitem>
    </varlistentry>
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index aac905fea4379..8a1793484e23e 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -1,20 +1,18 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   xcfg = config.services.xserver;
   cfg = xcfg.desktopManager.plasma5;
 
   libsForQt5 = pkgs.plasma5Packages;
   inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
   inherit (pkgs) writeText;
+  inherit (lib)
+    getBin optionalString
+    mkRemovedOptionModule mkRenamedOptionModule
+    mkDefault mkIf mkMerge mkOption types;
 
-  pulseaudio = config.hardware.pulseaudio;
-  pactl = "${getBin pulseaudio.package}/bin/pactl";
-  startplasma-x11 = "${getBin plasma5.plasma-workspace}/bin/startplasma-x11";
-  sed = "${getBin pkgs.gnused}/bin/sed";
+  ini = pkgs.formats.ini { };
 
   gtkrc2 = writeText "gtkrc-2.0" ''
     # Default GTK+ 2 config for NixOS Plasma 5
@@ -34,23 +32,25 @@ let
     gtk-button-images=1
   '';
 
-  gtk3_settings = writeText "settings.ini" ''
-    [Settings]
-    gtk-font-name=Sans Serif Regular 10
-    gtk-theme-name=Breeze
-    gtk-icon-theme-name=breeze
-    gtk-fallback-icon-theme=hicolor
-    gtk-cursor-theme-name=breeze_cursors
-    gtk-toolbar-style=GTK_TOOLBAR_ICONS
-    gtk-menu-images=1
-    gtk-button-images=1
-  '';
+  gtk3_settings = ini.generate "settings.ini" {
+    Settings = {
+      gtk-font-name = "Sans Serif Regular 10";
+      gtk-theme-name = "Breeze";
+      gtk-icon-theme-name = "breeze";
+      gtk-fallback-icon-theme = "hicolor";
+      gtk-cursor-theme-name = "breeze_cursors";
+      gtk-toolbar-style = "GTK_TOOLBAR_ICONS";
+      gtk-menu-images = 1;
+      gtk-button-images = 1;
+    };
+  };
 
-  kcminputrc = writeText "kcminputrc" ''
-    [Mouse]
-    cursorTheme=breeze_cursors
-    cursorSize=0
-  '';
+  kcminputrc = ini.generate "kcminputrc" {
+    Mouse = {
+      cursorTheme = "breeze_cursors";
+      cursorSize = 0;
+    };
+  };
 
   activationScript = ''
     ${set_XDG_CONFIG_HOME}
@@ -76,7 +76,7 @@ let
     # Qt from doing this wackiness in the first place.
     trolltech_conf="''${XDG_CONFIG_HOME}/Trolltech.conf"
     if [ -e "$trolltech_conf" ]; then
-        ${sed} -i "$trolltech_conf" -e '/nix\\store\|nix\/store/ d'
+      ${getBin pkgs.gnused}/bin/sed -i "$trolltech_conf" -e '/nix\\store\|nix\/store/ d'
     fi
 
     # Remove the kbuildsyscoca5 cache. It will be regenerated
@@ -88,92 +88,87 @@ let
   '';
 
   set_XDG_CONFIG_HOME = ''
-      # Set the default XDG_CONFIG_HOME if it is unset.
-      # Per the XDG Base Directory Specification:
-      # https://specifications.freedesktop.org/basedir-spec/latest
-      # 1. Never export this variable! If it is unset, then child processes are
-      # expected to set the default themselves.
-      # 2. Contaminate / if $HOME is unset; do not check if $HOME is set.
-      XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
+    # Set the default XDG_CONFIG_HOME if it is unset.
+    # Per the XDG Base Directory Specification:
+    # https://specifications.freedesktop.org/basedir-spec/latest
+    # 1. Never export this variable! If it is unset, then child processes are
+    # expected to set the default themselves.
+    # 2. Contaminate / if $HOME is unset; do not check if $HOME is set.
+    XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
   '';
 
-  startplasma =
-    ''
-      ${set_XDG_CONFIG_HOME}
-      mkdir -p "''${XDG_CONFIG_HOME}"
-
-    ''
-    + optionalString pulseaudio.enable ''
-      # Load PulseAudio module for routing support.
-      # See also: http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/
-        ${pactl} load-module module-device-manager "do_routing=1"
-
-    ''
-    + ''
-      ${activationScript}
-
-      # Create default configurations if Plasma has never been started.
-      kdeglobals="''${XDG_CONFIG_HOME}/kdeglobals"
-      if ! [ -f "$kdeglobals" ]
-      then
-          kcminputrc="''${XDG_CONFIG_HOME}/kcminputrc"
-          if ! [ -f "$kcminputrc" ]
-          then
-              cat ${kcminputrc} >"$kcminputrc"
-          fi
-
-          gtkrc2="$HOME/.gtkrc-2.0"
-          if ! [ -f "$gtkrc2" ]
-          then
-              cat ${gtkrc2} >"$gtkrc2"
-          fi
-
-          gtk3_settings="''${XDG_CONFIG_HOME}/gtk-3.0/settings.ini"
-          if ! [ -f "$gtk3_settings" ]
-          then
-              mkdir -p "$(dirname "$gtk3_settings")"
-              cat ${gtk3_settings} >"$gtk3_settings"
-          fi
+  startplasma = ''
+    ${set_XDG_CONFIG_HOME}
+    mkdir -p "''${XDG_CONFIG_HOME}"
+  '' + optionalString config.hardware.pulseaudio.enable ''
+    # Load PulseAudio module for routing support.
+    # See also: http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/
+      ${getBin config.hardware.pulseaudio.package}/bin/pactl load-module module-device-manager "do_routing=1"
+  '' + ''
+    ${activationScript}
+
+    # Create default configurations if Plasma has never been started.
+    kdeglobals="''${XDG_CONFIG_HOME}/kdeglobals"
+    if ! [ -f "$kdeglobals" ]; then
+      kcminputrc="''${XDG_CONFIG_HOME}/kcminputrc"
+      if ! [ -f "$kcminputrc" ]; then
+          cat ${kcminputrc} >"$kcminputrc"
       fi
 
-    ''
-    + ''
-      exec "${startplasma-x11}"
-    '';
+      gtkrc2="$HOME/.gtkrc-2.0"
+      if ! [ -f "$gtkrc2" ]; then
+          cat ${gtkrc2} >"$gtkrc2"
+      fi
+
+      gtk3_settings="''${XDG_CONFIG_HOME}/gtk-3.0/settings.ini"
+      if ! [ -f "$gtk3_settings" ]; then
+          mkdir -p "$(dirname "$gtk3_settings")"
+          cat ${gtk3_settings} >"$gtk3_settings"
+      fi
+    fi
+  '';
 
 in
 
 {
-  options = {
+  options.services.xserver.desktopManager.plasma5 = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the Plasma 5 (KDE 5) desktop environment.";
+    };
 
-    services.xserver.desktopManager.plasma5 = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Enable the Plasma 5 (KDE 5) desktop environment.";
-      };
+    phononBackend = mkOption {
+      type = types.enum [ "gstreamer" "vlc" ];
+      default = "gstreamer";
+      example = "vlc";
+      description = "Phonon audio backend to install.";
+    };
 
-      phononBackend = mkOption {
-        type = types.enum [ "gstreamer" "vlc" ];
-        default = "gstreamer";
-        example = "vlc";
-        description = "Phonon audio backend to install.";
-      };
+    supportDDC = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Support setting monitor brightness via DDC.
+        </para>
+        <para>
+        This is not needed for controlling brightness of the internal monitor
+        of a laptop and as it is considered experimental by upstream, it is
+        disabled by default.
+      '';
+    };
 
-      supportDDC = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Support setting monitor brightness via DDC.
-          </para>
-          <para>
-          This is not needed for controlling brightness of the internal monitor
-          of a laptop and as it is considered experimental by upstream, it is
-          disabled by default.
-        '';
-      };
+    useQtScaling = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable HiDPI scaling in Qt.";
     };
 
+    runUsingSystemd = mkOption {
+      description = "Use systemd to manage the Plasma session";
+      type = types.bool;
+      default = false;
+    };
   };
 
   imports = [
@@ -183,25 +178,40 @@ in
 
   config = mkMerge [
     (mkIf cfg.enable {
+
       # Seed our configuration into nixos-generate-config
-      system.nixos-generate-config.desktopConfiguration = [''
-        # Enable the Plasma 5 Desktop Environment.
-        services.xserver.displayManager.sddm.enable = true;
-        services.xserver.desktopManager.plasma5.enable = true;
-      ''];
-
-      services.xserver.desktopManager.session = singleton {
-        name = "plasma5";
-        bgSupport = true;
-        start = startplasma;
-      };
+      system.nixos-generate-config.desktopConfiguration = [
+        ''
+          # Enable the Plasma 5 Desktop Environment.
+          services.xserver.displayManager.sddm.enable = true;
+          services.xserver.desktopManager.plasma5.enable = true;
+        ''
+      ];
+
+      services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-workspace ];
+      # Default to be `plasma` (X11) instead of `plasmawayland`, since plasma wayland currently has
+      # many tiny bugs.
+      # See: https://github.com/NixOS/nixpkgs/issues/143272
+      services.xserver.displayManager.defaultSession = mkDefault "plasma";
 
       security.wrappers = {
-        kcheckpass.source = "${lib.getBin libsForQt5.kscreenlocker}/libexec/kcheckpass";
-        start_kdeinit.source = "${lib.getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
+        kcheckpass = {
+          setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${getBin libsForQt5.kscreenlocker}/libexec/kcheckpass";
+        };
+        start_kdeinit = {
+          setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
+        };
         kwin_wayland = {
-          source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
+          owner = "root";
+          group = "root";
           capabilities = "cap_sys_nice+ep";
+          source = "${getBin plasma5.kwin}/bin/kwin_wayland";
         };
       };
 
@@ -236,7 +246,7 @@ in
           kidletime
           kimageformats
           kinit
-          kirigami2  # In system profile for SDDM theme. TODO: wrapper.
+          kirigami2 # In system profile for SDDM theme. TODO: wrapper.
           kio
           kjobwidgets
           knewstuff
@@ -252,6 +262,7 @@ in
           kwallet-pam
           kwalletmanager
           kwayland
+          kwayland-integration
           kwidgetsaddons
           kxmlgui
           kxmlrpcclient
@@ -302,11 +313,16 @@ in
           breeze-icons
           pkgs.hicolor-icon-theme
 
-          kde-gtk-config breeze-gtk
+          kde-gtk-config
+          breeze-gtk
 
           qtvirtualkeyboard
 
           pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+
+          elisa
+          gwenview
+          okular
         ]
 
         # Phonon audio backend
@@ -320,6 +336,7 @@ in
         ++ lib.optional config.services.pipewire.pulse.enable plasma-pa
         ++ lib.optional config.powerManagement.enable powerdevil
         ++ lib.optional config.services.colord.enable pkgs.colord-kde
+        ++ lib.optional config.services.hardware.bolt.enable pkgs.plasma5Packages.plasma-thunderbolt
         ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
         ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet;
 
@@ -330,6 +347,8 @@ in
 
       environment.etc."X11/xkb".source = xcfg.xkbDir;
 
+      environment.sessionVariables.PLASMA_USE_QT_SCALING = mkIf cfg.useQtScaling "1";
+
       # Enable GTK applications to load SVG icons
       services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
 
@@ -343,9 +362,12 @@ in
       programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
 
       # Enable helpful DBus services.
+      services.accounts-daemon.enable = true;
+      # when changing an account picture the accounts-daemon reads a temporary file containing the image which systemsettings5 may place under /tmp
+      systemd.services.accounts-daemon.serviceConfig.PrivateTmp = false;
       services.udisks2.enable = true;
       services.upower.enable = config.powerManagement.enable;
-      services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+      services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true);
       services.xserver.libinput.enable = mkDefault true;
 
       # Extra UDEV rules used by Solid
@@ -367,14 +389,35 @@ in
       security.pam.services.lightdm.enableKwallet = true;
       security.pam.services.sddm.enableKwallet = true;
 
+      systemd.user.services = {
+        plasma-early-setup = mkIf cfg.runUsingSystemd {
+          description = "Early Plasma setup";
+          wantedBy = [ "graphical-session-pre.target" ];
+          serviceConfig.Type = "oneshot";
+          script = activationScript;
+        };
+
+        plasma-run-with-systemd = {
+          description = "Run KDE Plasma via systemd";
+          wantedBy = [ "basic.target" ];
+          serviceConfig.Type = "oneshot";
+          script = ''
+            ${set_XDG_CONFIG_HOME}
+
+            ${kdeFrameworks.kconfig}/bin/kwriteconfig5 \
+              --file startkderc --group General --key systemdBoot ${lib.boolToString cfg.runUsingSystemd}
+          '';
+        };
+      };
+
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ];
 
       # Update the start menu for each user that is currently logged in
       system.userActivationScripts.plasmaSetup = activationScript;
+      services.xserver.displayManager.setupCommands = startplasma;
 
       nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
     })
   ];
-
 }
diff --git a/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixos/modules/services/x11/desktop-managers/surf-display.nix
index 9aeb0bbd2a887..4b5a04f988ba9 100644
--- a/nixos/modules/services/x11/desktop-managers/surf-display.nix
+++ b/nixos/modules/services/x11/desktop-managers/surf-display.nix
@@ -50,6 +50,7 @@ in {
       defaultWwwUri = mkOption {
         type = types.str;
         default = "${pkgs.surf-display}/share/surf-display/empty-page.html";
+        defaultText = literalExpression ''"''${pkgs.surf-display}/share/surf-display/empty-page.html"'';
         example = "https://www.example.com/";
         description = "Default URI to display.";
       };
@@ -57,7 +58,7 @@ in {
       inactivityInterval = mkOption {
         type = types.int;
         default = 300;
-        example = "0";
+        example = 0;
         description = ''
           Setting for internal inactivity timer to restart surf-display if the
           user goes inactive/idle to get a fresh session for the next user of
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index bbfdea2225b58..25276e1d649ec 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -49,7 +49,7 @@ in
       thunarPlugins = mkOption {
         default = [];
         type = types.listOf types.package;
-        example = literalExample "[ pkgs.xfce.thunar-archive-plugin ]";
+        example = literalExpression "[ pkgs.xfce.thunar-archive-plugin ]";
         description = ''
           A list of plugin that should be installed with Thunar.
         '';
diff --git a/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixos/modules/services/x11/desktop-managers/xterm.nix
index f76db278a927d..3424ee1b0e113 100644
--- a/nixos/modules/services/x11/desktop-managers/xterm.nix
+++ b/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -14,8 +14,8 @@ in
 
     services.xserver.desktopManager.xterm.enable = mkOption {
       type = types.bool;
-      default = (versionOlder config.system.stateVersion "19.09") && xSessionEnabled;
-      defaultText = if versionOlder config.system.stateVersion "19.09" then "config.services.xserver.enable" else "false";
+      default = versionOlder config.system.stateVersion "19.09" && xSessionEnabled;
+      defaultText = literalExpression ''versionOlder config.system.stateVersion "19.09" && config.services.xserver.enable;'';
       description = "Enable a xterm terminal as a desktop manager.";
     };
 
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 584dfb63c4dc5..bdc46faa7fd0c 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -122,10 +122,10 @@ let
         done
 
         if test -d ${pkg}/share/xsessions; then
-          ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
+          ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
         fi
         if test -d ${pkg}/share/wayland-sessions; then
-          ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions
+          ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions
         fi
       '') cfg.displayManager.sessionPackages}
     '';
@@ -217,7 +217,7 @@ in
 
       session = mkOption {
         default = [];
-        example = literalExample
+        example = literalExpression
           ''
             [ { manage = "desktop";
                 name = "xterm";
@@ -280,7 +280,7 @@ in
             null;
         example = "gnome";
         description = ''
-          Graphical session to pre-select in the session chooser (only effective for GDM and LightDM).
+          Graphical session to pre-select in the session chooser (only effective for GDM, LightDM and SDDM).
 
           On GDM, LightDM and SDDM, it will also be used as a session for auto-login.
         '';
@@ -305,9 +305,7 @@ in
 
         execCmd = mkOption {
           type = types.str;
-          example = literalExample ''
-            "''${pkgs.lightdm}/bin/lightdm"
-          '';
+          example = literalExpression ''"''${pkgs.lightdm}/bin/lightdm"'';
           description = "Command to start the display manager.";
         };
 
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 5c4c6c67fd022..e036c684c886a 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -6,6 +6,8 @@ let
 
   cfg = config.services.xserver.displayManager;
   gdm = pkgs.gnome.gdm;
+  settingsFormat = pkgs.formats.ini { };
+  configFile = settingsFormat.generate "custom.conf" cfg.gdm.settings;
 
   xSessionWrapper = if (cfg.setupCommands == "") then null else
     pkgs.writeScript "gdm-x-session-wrapper" ''
@@ -24,7 +26,6 @@ let
     load-module module-udev-detect
     load-module module-native-protocol-unix
     load-module module-default-device-restore
-    load-module module-rescue-streams
     load-module module-always-sink
     load-module module-intended-roles
     load-module module-suspend-on-idle
@@ -105,6 +106,18 @@ in
         type = types.bool;
       };
 
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = {
+          debug.enable = true;
+        };
+        description = ''
+          Options passed to the gdm daemon.
+          See <link xlink:href="https://help.gnome.org/admin/gdm/stable/configuration.html.en#daemonconfig">here</link> for supported options.
+        '';
+      };
+
     };
 
   };
@@ -174,9 +187,6 @@ in
       "systemd-machined.service"
       # setSessionScript wants AccountsService
       "accounts-daemon.service"
-      # Failed to open gpu '/dev/dri/card0': GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: Operation not permitted
-      # https://github.com/NixOS/nixpkgs/pull/25311#issuecomment-609417621
-      "systemd-udev-settle.service"
     ];
 
     systemd.services.display-manager.after = [
@@ -186,7 +196,6 @@ in
       "getty@tty${gdm.initialVT}.service"
       "plymouth-quit.service"
       "plymouth-start.service"
-      "systemd-udev-settle.service"
     ];
     systemd.services.display-manager.conflicts = [
       "getty@tty${gdm.initialVT}.service"
@@ -274,31 +283,26 @@ in
     # Use AutomaticLogin if delay is zero, because it's immediate.
     # Otherwise with TimedLogin with zero seconds the prompt is still
     # presented and there's a little delay.
-    environment.etc."gdm/custom.conf".text = ''
-      [daemon]
-      WaylandEnable=${boolToString cfg.gdm.wayland}
-      ${optionalString cfg.autoLogin.enable (
-        if cfg.gdm.autoLogin.delay > 0 then ''
-          TimedLoginEnable=true
-          TimedLogin=${cfg.autoLogin.user}
-          TimedLoginDelay=${toString cfg.gdm.autoLogin.delay}
-        '' else ''
-          AutomaticLoginEnable=true
-          AutomaticLogin=${cfg.autoLogin.user}
-        '')
-      }
-
-      [security]
-
-      [xdmcp]
-
-      [greeter]
-
-      [chooser]
-
-      [debug]
-      ${optionalString cfg.gdm.debug "Enable=true"}
-    '';
+    services.xserver.displayManager.gdm.settings = {
+      daemon = mkMerge [
+        { WaylandEnable = cfg.gdm.wayland; }
+        # nested if else didn't work
+        (mkIf (cfg.autoLogin.enable && cfg.gdm.autoLogin.delay != 0 ) {
+          TimedLoginEnable = true;
+          TimedLogin = cfg.autoLogin.user;
+          TimedLoginDelay = cfg.gdm.autoLogin.delay;
+        })
+        (mkIf (cfg.autoLogin.enable && cfg.gdm.autoLogin.delay == 0 ) {
+          AutomaticLoginEnable = true;
+          AutomaticLogin = cfg.autoLogin.user;
+        })
+      ];
+      debug = mkIf cfg.gdm.debug {
+        Enable = true;
+      };
+    };
+
+    environment.etc."gdm/custom.conf".source = configFile;
 
     environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.sessionData.wrapper;
 
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
index ecd46a9ee6d25..930ee96b384d5 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -35,7 +35,7 @@ in {
         package = mkOption {
           type = types.package;
           default = pkgs.gnome.gnome-themes-extra;
-          defaultText = "pkgs.gnome.gnome-themes-extra";
+          defaultText = literalExpression "pkgs.gnome.gnome-themes-extra";
           description = ''
             The package path that contains the theme given in the name option.
           '';
@@ -54,7 +54,7 @@ in {
         package = mkOption {
           type = types.package;
           default = pkgs.papirus-icon-theme;
-          defaultText = "pkgs.papirus-icon-theme";
+          defaultText = literalExpression "pkgs.papirus-icon-theme";
           description = ''
             The package path that contains the icon theme given in the name option.
           '';
@@ -73,7 +73,7 @@ in {
         package = mkOption {
           type = types.package;
           default = pkgs.capitaine-cursors;
-          defaultText = "pkgs.capitaine-cursors";
+          defaultText = literalExpression "pkgs.capitaine-cursors";
           description = ''
             The package path that contains the cursor theme given in the name option.
           '';
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
index fe5a16bc60f15..debd4b568bf65 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -48,7 +48,7 @@ in
         package = mkOption {
           type = types.package;
           default = pkgs.gnome.gnome-themes-extra;
-          defaultText = "pkgs.gnome.gnome-themes-extra";
+          defaultText = literalExpression "pkgs.gnome.gnome-themes-extra";
           description = ''
             The package path that contains the theme given in the name option.
           '';
@@ -69,7 +69,7 @@ in
         package = mkOption {
           type = types.package;
           default = pkgs.gnome.adwaita-icon-theme;
-          defaultText = "pkgs.gnome.adwaita-icon-theme";
+          defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
           description = ''
             The package path that contains the icon theme given in the name option.
           '';
@@ -90,7 +90,7 @@ in
         package = mkOption {
           type = types.package;
           default = pkgs.gnome.adwaita-icon-theme;
-          defaultText = "pkgs.gnome.adwaita-icon-theme";
+          defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
           description = ''
             The package path that contains the cursor theme given in the name option.
           '';
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 41c1b635f5d6a..9a7532b476415 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -146,9 +146,9 @@ in
       };
 
       background = mkOption {
-        type = types.path;
+        type = types.either types.path (types.strMatching "^#[0-9]\{6\}$");
         # Manual cannot depend on packages, we are actually setting the default in config below.
-        defaultText = "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
+        defaultText = literalExpression "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
         description = ''
           The background image or color to use.
         '';
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index d79b3cda2fcc5..529a086381f06 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -30,6 +30,9 @@ let
       HaltCommand = "/run/current-system/systemd/bin/systemctl poweroff";
       RebootCommand = "/run/current-system/systemd/bin/systemctl reboot";
       Numlock = if cfg.autoNumlock then "on" else "none"; # on, off none
+
+      # Implementation is done via pkgs/applications/display-managers/sddm/sddm-default-session.patch
+      DefaultSession = optionalString (dmcfg.defaultSession != null) "${dmcfg.defaultSession}.desktop";
     };
 
     Theme = {
@@ -113,14 +116,12 @@ in
       settings = mkOption {
         type = iniFmt.type;
         default = { };
-        example = ''
-          {
-            Autologin = {
-              User = "john";
-              Session = "plasma.desktop";
-            };
-          }
-        '';
+        example = {
+          Autologin = {
+            User = "john";
+            Session = "plasma.desktop";
+          };
+        };
         description = ''
           Extra settings merged in and overwritting defaults in sddm.conf.
         '';
diff --git a/nixos/modules/services/x11/display-managers/startx.nix b/nixos/modules/services/x11/display-managers/startx.nix
index 6cd46cdf96498..a48566ae06841 100644
--- a/nixos/modules/services/x11/display-managers/startx.nix
+++ b/nixos/modules/services/x11/display-managers/startx.nix
@@ -35,10 +35,7 @@ in
   config = mkIf cfg.enable {
     services.xserver = {
       exportConfiguration = true;
-      displayManager.job.execCmd = "";
-      displayManager.lightdm.enable = lib.mkForce false;
     };
-    systemd.services.display-manager.enable = false;
 
     # Other displayManagers log to /dev/null because they're services and put
     # Xorg's stdout in the journal
diff --git a/nixos/modules/services/x11/display-managers/sx.nix b/nixos/modules/services/x11/display-managers/sx.nix
index 132531c0ddc0c..e309773643009 100644
--- a/nixos/modules/services/x11/display-managers/sx.nix
+++ b/nixos/modules/services/x11/display-managers/sx.nix
@@ -26,12 +26,9 @@ in {
     environment.systemPackages = [ pkgs.sx ];
     services.xserver = {
       exportConfiguration = true;
-      displayManager = {
-        job.execCmd = "";
-        lightdm.enable = mkForce false;
-      };
       logFile = mkDefault null;
     };
-    systemd.services.display-manager.enable = false;
   };
+
+  meta.maintainers = with maintainers; [ figsoda ];
 }
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
index f48216ff446f5..159bed63e137d 100644
--- a/nixos/modules/services/x11/extra-layouts.nix
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -79,6 +79,10 @@ let
     };
   };
 
+  xkb_patched = pkgs.xorg.xkeyboardconfig_custom {
+    layouts = config.services.xserver.extraLayouts;
+  };
+
 in
 
 {
@@ -89,7 +93,7 @@ in
     extraLayouts = mkOption {
       type = types.attrsOf (types.submodule layoutOpts);
       default = {};
-      example = literalExample
+      example = literalExpression
       ''
         {
           mine = {
@@ -114,59 +118,16 @@ in
 
   config = mkIf (layouts != { }) {
 
-    # We don't override xkeyboard_config directly to
-    # reduce the amount of packages to be recompiled.
-    # Only the following packages are necessary to set
-    # a custom layout anyway:
-    nixpkgs.overlays = lib.singleton (self: super: {
-
-      xkb_patched = self.xorg.xkeyboardconfig_custom {
-        layouts = config.services.xserver.extraLayouts;
-      };
-
-      xorg = super.xorg // {
-        xorgserver = super.xorg.xorgserver.overrideAttrs (old: {
-          configureFlags = old.configureFlags ++ [
-            "--with-xkb-bin-directory=${self.xorg.xkbcomp}/bin"
-            "--with-xkb-path=${self.xkb_patched}/share/X11/xkb"
-          ];
-        });
-
-        setxkbmap = super.xorg.setxkbmap.overrideAttrs (old: {
-          postInstall =
-            ''
-              mkdir -p $out/share
-              ln -sfn ${self.xkb_patched}/etc/X11 $out/share/X11
-            '';
-        });
-
-        xkbcomp = super.xorg.xkbcomp.overrideAttrs (old: {
-          configureFlags = [ "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb" ];
-        });
-
-      };
-
-      ckbcomp = super.ckbcomp.override {
-        xkeyboard_config = self.xkb_patched;
-      };
-
-      xkbvalidate = super.xkbvalidate.override {
-        libxkbcommon = self.libxkbcommon.override {
-          xkeyboard_config = self.xkb_patched;
-        };
-      };
-
-    });
-
     environment.sessionVariables = {
       # runtime override supported by multiple libraries e. g. libxkbcommon
       # https://xkbcommon.org/doc/current/group__include-path.html
-      XKB_CONFIG_ROOT = "${pkgs.xkb_patched}/etc/X11/xkb";
+      XKB_CONFIG_ROOT = "${xkb_patched}/etc/X11/xkb";
     };
 
     services.xserver = {
-      xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb";
-      exportConfiguration = config.services.xserver.displayManager.startx.enable;
+      xkbDir = "${xkb_patched}/etc/X11/xkb";
+      exportConfiguration = config.services.xserver.displayManager.startx.enable
+        || config.services.xserver.displayManager.sx.enable;
     };
 
   };
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index 439708bc47ed0..efdb7c61dfaeb 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -13,7 +13,7 @@ let cfg = config.services.xserver.libinput;
         example = "/dev/input/event0";
         description =
           ''
-            Path for ${deviceType} device.  Set to null to apply to any
+            Path for ${deviceType} device.  Set to <literal>null</literal> to apply to any
             auto-detected ${deviceType}.
           '';
       };
@@ -24,8 +24,8 @@ let cfg = config.services.xserver.libinput;
         example = "flat";
         description =
           ''
-            Sets  the pointer acceleration profile to the given profile.
-            Permitted values are adaptive, flat.
+            Sets the pointer acceleration profile to the given profile.
+            Permitted values are <literal>adaptive</literal>, <literal>flat</literal>.
             Not all devices support this option or all profiles.
             If a profile is unsupported, the default profile for this is used.
             <literal>flat</literal>: Pointer motion is accelerated by a constant
@@ -38,12 +38,14 @@ let cfg = config.services.xserver.libinput;
       accelSpeed = mkOption {
         type = types.nullOr types.str;
         default = null;
+        example = "-0.5";
         description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
       };
 
       buttonMapping = mkOption {
         type = types.nullOr types.str;
         default = null;
+        example = "1 6 3 4 5 0 7";
         description =
           ''
             Sets the logical button mapping for this device, see XSetPointerMapping(3). The string  must
@@ -58,9 +60,10 @@ let cfg = config.services.xserver.libinput;
       calibrationMatrix = mkOption {
         type = types.nullOr types.str;
         default = null;
+        example = "0.5 0 0 0 0.8 0.1 0 0 1";
         description =
           ''
-            A  string  of  9 space-separated floating point numbers.  Sets the calibration matrix to the
+            A string of 9 space-separated floating point numbers. Sets the calibration matrix to the
             3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
           '';
       };
@@ -68,6 +71,7 @@ let cfg = config.services.xserver.libinput;
       clickMethod = mkOption {
         type = types.nullOr (types.enum [ "none" "buttonareas" "clickfinger" ]);
         default = null;
+        example = "buttonareas";
         description =
           ''
             Enables a click method. Permitted values are <literal>none</literal>,
@@ -163,6 +167,16 @@ let cfg = config.services.xserver.libinput;
           '';
       };
 
+      transformationMatrix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.5 0 0 0 0.8 0.1 0 0 1";
+        description = ''
+          A string of 9 space-separated floating point numbers. Sets the transformation matrix to
+          the 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
+        '';
+      };
+
       disableWhileTyping = mkOption {
         type = types.bool;
         default = false;
@@ -196,6 +210,7 @@ let cfg = config.services.xserver.libinput;
       ${optionalString (cfg.${deviceType}.accelSpeed != null) ''Option "AccelSpeed" "${cfg.${deviceType}.accelSpeed}"''}
       ${optionalString (cfg.${deviceType}.buttonMapping != null) ''Option "ButtonMapping" "${cfg.${deviceType}.buttonMapping}"''}
       ${optionalString (cfg.${deviceType}.calibrationMatrix != null) ''Option "CalibrationMatrix" "${cfg.${deviceType}.calibrationMatrix}"''}
+      ${optionalString (cfg.${deviceType}.transformationMatrix != null) ''Option "TransformationMatrix" "${cfg.${deviceType}.transformationMatrix}"''}
       ${optionalString (cfg.${deviceType}.clickMethod != null) ''Option "ClickMethod" "${cfg.${deviceType}.clickMethod}"''}
       Option "LeftHanded" "${xorgBool cfg.${deviceType}.leftHanded}"
       Option "MiddleEmulation" "${xorgBool cfg.${deviceType}.middleEmulation}"
@@ -227,6 +242,7 @@ in {
       "sendEventsMode"
       "tapping"
       "tappingDragLock"
+      "transformationMatrix"
       "disableWhileTyping"
       "additionalOptions"
     ]);
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
index 51f72dadbd43e..ae990141a5029 100644
--- a/nixos/modules/services/x11/imwheel.nix
+++ b/nixos/modules/services/x11/imwheel.nix
@@ -21,15 +21,17 @@ in
         rules = mkOption {
           type = types.attrsOf types.str;
           default = {};
-          example = literalExample ''
-            ".*" = '''
-              None,      Up,   Button4, 8
-              None,      Down, Button5, 8
-              Shift_L,   Up,   Shift_L|Button4, 4
-              Shift_L,   Down, Shift_L|Button5, 4
-              Control_L, Up,   Control_L|Button4
-              Control_L, Down, Control_L|Button5
-            ''';
+          example = literalExpression ''
+            {
+              ".*" = '''
+                None,      Up,   Button4, 8
+                None,      Down, Button5, 8
+                Shift_L,   Up,   Shift_L|Button4, 4
+                Shift_L,   Down, Shift_L|Button5, 4
+                Control_L, Up,   Control_L|Button4
+                Control_L, Down, Control_L|Button5
+              ''';
+            }
           '';
           description = ''
             Window class translation rules.
diff --git a/nixos/modules/services/x11/picom.nix b/nixos/modules/services/x11/picom.nix
index 977d0fea21924..dbd4b1cefef18 100644
--- a/nixos/modules/services/x11/picom.nix
+++ b/nixos/modules/services/x11/picom.nix
@@ -254,7 +254,7 @@ in {
     in mkOption {
       type = topLevel;
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         blur =
           { method = "gaussian";
             size = 10;
diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix
index 60d80a28762b0..cc9f964754f35 100644
--- a/nixos/modules/services/x11/redshift.nix
+++ b/nixos/modules/services/x11/redshift.nix
@@ -76,7 +76,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.redshift;
-      defaultText = "pkgs.redshift";
+      defaultText = literalExpression "pkgs.redshift";
       description = ''
         redshift derivation to use.
       '';
diff --git a/nixos/modules/services/x11/touchegg.nix b/nixos/modules/services/x11/touchegg.nix
new file mode 100644
index 0000000000000..9d3678e7696df
--- /dev/null
+++ b/nixos/modules/services/x11/touchegg.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.touchegg;
+
+in {
+  meta = {
+    maintainers = teams.pantheon.members;
+  };
+
+  ###### interface
+  options.services.touchegg = {
+    enable = mkEnableOption "touchegg, a multi-touch gesture recognizer";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.touchegg;
+      defaultText = literalExpression "pkgs.touchegg";
+      description = "touchegg derivation to use.";
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.services.touchegg = {
+      description = "Touchegg Daemon";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${cfg.package}/bin/touchegg --daemon";
+        Restart = "on-failure";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/x11/unclutter-xfixes.nix b/nixos/modules/services/x11/unclutter-xfixes.nix
index 71262431b6853..0b4d06f640d24 100644
--- a/nixos/modules/services/x11/unclutter-xfixes.nix
+++ b/nixos/modules/services/x11/unclutter-xfixes.nix
@@ -17,7 +17,7 @@ in {
       description = "unclutter-xfixes derivation to use.";
       type = types.package;
       default = pkgs.unclutter-xfixes;
-      defaultText = "pkgs.unclutter-xfixes";
+      defaultText = literalExpression "pkgs.unclutter-xfixes";
     };
 
     timeout = mkOption {
diff --git a/nixos/modules/services/x11/unclutter.nix b/nixos/modules/services/x11/unclutter.nix
index 56e30c79d1f1a..bdb5fa7b50cd7 100644
--- a/nixos/modules/services/x11/unclutter.nix
+++ b/nixos/modules/services/x11/unclutter.nix
@@ -16,7 +16,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.unclutter;
-      defaultText = "pkgs.unclutter";
+      defaultText = literalExpression "pkgs.unclutter";
       description = "unclutter derivation to use.";
     };
 
diff --git a/nixos/modules/services/x11/urxvtd.nix b/nixos/modules/services/x11/urxvtd.nix
index 867ac38a944ff..0a0df447f4e16 100644
--- a/nixos/modules/services/x11/urxvtd.nix
+++ b/nixos/modules/services/x11/urxvtd.nix
@@ -19,7 +19,7 @@ in {
 
     package = mkOption {
       default = pkgs.rxvt-unicode;
-      defaultText = "pkgs.rxvt-unicode";
+      defaultText = literalExpression "pkgs.rxvt-unicode";
       description = ''
         Package to install. Usually pkgs.rxvt-unicode.
       '';
diff --git a/nixos/modules/services/x11/window-managers/awesome.nix b/nixos/modules/services/x11/window-managers/awesome.nix
index 37a14e34f57e5..c6c0c934f9aeb 100644
--- a/nixos/modules/services/x11/window-managers/awesome.nix
+++ b/nixos/modules/services/x11/window-managers/awesome.nix
@@ -27,7 +27,7 @@ in
         default = [];
         type = types.listOf types.package;
         description = "List of lua packages available for being used in the Awesome configuration.";
-        example = literalExample "[ pkgs.luaPackages.vicious ]";
+        example = literalExpression "[ pkgs.luaPackages.vicious ]";
       };
 
       package = mkOption {
diff --git a/nixos/modules/services/x11/window-managers/bspwm.nix b/nixos/modules/services/x11/window-managers/bspwm.nix
index 23cd4f6529a60..ade24061a069a 100644
--- a/nixos/modules/services/x11/window-managers/bspwm.nix
+++ b/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -14,15 +14,15 @@ in
       package = mkOption {
         type        = types.package;
         default     = pkgs.bspwm;
-        defaultText = "pkgs.bspwm";
-        example     = "pkgs.bspwm-unstable";
+        defaultText = literalExpression "pkgs.bspwm";
+        example     = literalExpression "pkgs.bspwm-unstable";
         description = ''
           bspwm package to use.
         '';
       };
       configFile = mkOption {
         type        = with types; nullOr path;
-        example     = "${pkgs.bspwm}/share/doc/bspwm/examples/bspwmrc";
+        example     = literalExpression ''"''${pkgs.bspwm}/share/doc/bspwm/examples/bspwmrc"'';
         default     = null;
         description = ''
           Path to the bspwm configuration file.
@@ -34,15 +34,15 @@ in
         package = mkOption {
           type        = types.package;
           default     = pkgs.sxhkd;
-          defaultText = "pkgs.sxhkd";
-          example     = "pkgs.sxhkd-unstable";
+          defaultText = literalExpression "pkgs.sxhkd";
+          example     = literalExpression "pkgs.sxhkd-unstable";
           description = ''
             sxhkd package to use.
           '';
         };
         configFile = mkOption {
           type        = with types; nullOr path;
-          example     = "${pkgs.bspwm}/share/doc/bspwm/examples/sxhkdrc";
+          example     = literalExpression ''"''${pkgs.bspwm}/share/doc/bspwm/examples/sxhkdrc"'';
           default     = null;
           description = ''
             Path to the sxhkd configuration file.
diff --git a/nixos/modules/services/x11/window-managers/clfswm.nix b/nixos/modules/services/x11/window-managers/clfswm.nix
index 5015852db69f8..78772c799744e 100644
--- a/nixos/modules/services/x11/window-managers/clfswm.nix
+++ b/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -13,7 +13,7 @@ in
       package = mkOption {
         type        = types.package;
         default     = pkgs.lispPackages.clfswm;
-        defaultText = "pkgs.lispPackages.clfswm";
+        defaultText = literalExpression "pkgs.lispPackages.clfswm";
         description = ''
           clfswm package to use.
         '';
diff --git a/nixos/modules/services/x11/window-managers/exwm.nix b/nixos/modules/services/x11/window-managers/exwm.nix
index 4b707d3984969..b505f720f04c3 100644
--- a/nixos/modules/services/x11/window-managers/exwm.nix
+++ b/nixos/modules/services/x11/window-managers/exwm.nix
@@ -22,7 +22,7 @@ in
       loadScript = mkOption {
         default = "(require 'exwm)";
         type = types.lines;
-        example = literalExample ''
+        example = ''
           (require 'exwm)
           (exwm-enable)
         '';
@@ -39,8 +39,9 @@ in
       };
       extraPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
-        default = self: [];
-        example = literalExample ''
+        default = epkgs: [];
+        defaultText = literalExpression "epkgs: []";
+        example = literalExpression ''
           epkgs: [
             epkgs.emms
             epkgs.magit
diff --git a/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
index 548097a412d23..354d70c695cb5 100644
--- a/nixos/modules/services/x11/window-managers/herbstluftwm.nix
+++ b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -14,7 +14,7 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.herbstluftwm;
-        defaultText = "pkgs.herbstluftwm";
+        defaultText = literalExpression "pkgs.herbstluftwm";
         description = ''
           Herbstluftwm package to use.
         '';
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index 0ef55d5f2c036..99f9997024fe3 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -30,8 +30,8 @@ in
     package = mkOption {
       type        = types.package;
       default     = pkgs.i3;
-      defaultText = "pkgs.i3";
-      example     = "pkgs.i3-gaps";
+      defaultText = literalExpression "pkgs.i3";
+      example     = literalExpression "pkgs.i3-gaps";
       description = ''
         i3 package to use.
       '';
@@ -40,7 +40,7 @@ in
     extraPackages = mkOption {
       type = with types; listOf package;
       default = with pkgs; [ dmenu i3status i3lock ];
-      example = literalExample ''
+      defaultText = literalExpression ''
         with pkgs; [
           dmenu
           i3status
diff --git a/nixos/modules/services/x11/window-managers/wmderland.nix b/nixos/modules/services/x11/window-managers/wmderland.nix
index a6864a827719b..56b6922096517 100644
--- a/nixos/modules/services/x11/window-managers/wmderland.nix
+++ b/nixos/modules/services/x11/window-managers/wmderland.nix
@@ -28,7 +28,7 @@ in
         feh
         rxvt-unicode
       ];
-      example = literalExample ''
+      defaultText = literalExpression ''
         with pkgs; [
           rofi
           dunst
diff --git a/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index fe8ed38125114..6aa0d5f76f264 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -2,7 +2,7 @@
 
 with lib;
 let
-  inherit (lib) mkOption mkIf optionals literalExample;
+  inherit (lib) mkOption mkIf optionals literalExpression;
   cfg = config.services.xserver.windowManager.xmonad;
 
   ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
@@ -42,8 +42,8 @@ in {
       enable = mkEnableOption "xmonad";
       haskellPackages = mkOption {
         default = pkgs.haskellPackages;
-        defaultText = "pkgs.haskellPackages";
-        example = literalExample "pkgs.haskell.packages.ghc784";
+        defaultText = literalExpression "pkgs.haskellPackages";
+        example = literalExpression "pkgs.haskell.packages.ghc784";
         description = ''
           haskellPackages used to build Xmonad and other packages.
           This can be used to change the GHC version used to build
@@ -55,8 +55,8 @@ in {
       extraPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
         default = self: [];
-        defaultText = "self: []";
-        example = literalExample ''
+        defaultText = literalExpression "self: []";
+        example = literalExpression ''
           haskellPackages: [
             haskellPackages.xmonad-contrib
             haskellPackages.monad-logger
diff --git a/nixos/modules/services/x11/xautolock.nix b/nixos/modules/services/x11/xautolock.nix
index 5ce08fce7c434..947d8f4edfb59 100644
--- a/nixos/modules/services/x11/xautolock.nix
+++ b/nixos/modules/services/x11/xautolock.nix
@@ -27,7 +27,8 @@ in
 
         locker = mkOption {
           default = "${pkgs.xlockmore}/bin/xlock"; # default according to `man xautolock`
-          example = "${pkgs.i3lock}/bin/i3lock -i /path/to/img";
+          defaultText = literalExpression ''"''${pkgs.xlockmore}/bin/xlock"'';
+          example = literalExpression ''"''${pkgs.i3lock}/bin/i3lock -i /path/to/img"'';
           type = types.str;
 
           description = ''
@@ -37,7 +38,7 @@ in
 
         nowlocker = mkOption {
           default = null;
-          example = "${pkgs.i3lock}/bin/i3lock -i /path/to/img";
+          example = literalExpression ''"''${pkgs.i3lock}/bin/i3lock -i /path/to/img"'';
           type = types.nullOr types.str;
 
           description = ''
@@ -56,7 +57,7 @@ in
 
         notifier = mkOption {
           default = null;
-          example = "${pkgs.libnotify}/bin/notify-send \"Locking in 10 seconds\"";
+          example = literalExpression ''"''${pkgs.libnotify}/bin/notify-send 'Locking in 10 seconds'"'';
           type = types.nullOr types.str;
 
           description = ''
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index ad9bd88f98aa4..24d9257344235 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -217,7 +217,7 @@ in
       inputClassSections = mkOption {
         type = types.listOf types.lines;
         default = [];
-        example = literalExample ''
+        example = literalExpression ''
           [ '''
               Identifier      "Trackpoint Wheel Emulation"
               MatchProduct    "ThinkPad USB Keyboard with TrackPoint"
@@ -233,7 +233,7 @@ in
       modules = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ pkgs.xf86_input_wacom ]";
+        example = literalExpression "[ pkgs.xf86_input_wacom ]";
         description = "Packages to be added to the module search path of the X server.";
       };
 
@@ -351,6 +351,7 @@ in
       xkbDir = mkOption {
         type = types.path;
         default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
+        defaultText = literalExpression ''"''${pkgs.xkeyboard_config}/etc/X11/xkb"'';
         description = ''
           Path used for -xkbdir xserver parameter.
         '';
@@ -587,11 +588,22 @@ in
   config = mkIf cfg.enable {
 
     services.xserver.displayManager.lightdm.enable =
-      let dmconf = cfg.displayManager;
-          default = !(dmconf.gdm.enable
-                    || dmconf.sddm.enable
-                    || dmconf.xpra.enable );
-      in mkIf (default) true;
+      let dmConf = cfg.displayManager;
+          default = !(dmConf.gdm.enable
+                    || dmConf.sddm.enable
+                    || dmConf.xpra.enable
+                    || dmConf.sx.enable
+                    || dmConf.startx.enable);
+      in mkIf (default) (mkDefault true);
+
+    # so that the service won't be enabled when only startx is used
+    systemd.services.display-manager.enable  =
+      let dmConf = cfg.displayManager;
+          noDmUsed = !(dmConf.gdm.enable
+                    || dmConf.sddm.enable
+                    || dmConf.xpra.enable
+                    || dmConf.lightdm.enable);
+      in mkIf (noDmUsed) (mkDefault false);
 
     hardware.opengl.enable = mkDefault true;
 
@@ -701,7 +713,8 @@ in
             rm -f /tmp/.X0-lock
           '';
 
-        script = "${cfg.displayManager.job.execCmd}";
+        # TODO: move declaring the systemd service to its own mkIf
+        script = mkIf (config.systemd.services.display-manager.enable == true) "${cfg.displayManager.job.execCmd}";
 
         # Stop restarting if the display manager stops (crashes) 2 times
         # in one minute. Starting X typically takes 3-4s.
@@ -738,6 +751,9 @@ in
       nativeBuildInputs = with pkgs.buildPackages; [ xkbvalidate ];
       preferLocalBuild = true;
     } ''
+      ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
+        "export XKB_CONFIG_ROOT=${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
+      }
       xkbvalidate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
       touch "$out"
     '');
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index 548b4de852b7b..8dbfe393f109b 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -18,8 +18,17 @@ let
   });
 
   systemActivationScript = set: onlyDry: let
-    set' = filterAttrs (_: v: onlyDry -> v.supportsDryActivation) (mapAttrs (_: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v) set);
+    set' = mapAttrs (_: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v) set;
     withHeadlines = addAttributeName set';
+    # When building a dry activation script, this replaces all activation scripts
+    # that do not support dry mode with a comment that does nothing. Filtering these
+    # activation scripts out so they don't get generated into the dry activation script
+    # does not work because when an activation script that supports dry mode depends on
+    # an activation script that does not, the dependency cannot be resolved and the eval
+    # fails.
+    withDrySnippets = mapAttrs (a: v: if onlyDry && !v.supportsDryActivation then v // {
+      text = "#### Activation script snippet ${a} does not support dry activation.";
+    } else v) withHeadlines;
   in
     ''
       #!${pkgs.runtimeShell}
@@ -37,7 +46,7 @@ let
       # Ensure a consistent umask.
       umask 0022
 
-      ${textClosureMap id (withHeadlines) (attrNames withHeadlines)}
+      ${textClosureMap id (withDrySnippets) (attrNames withDrySnippets)}
 
     '' + optionalString (!onlyDry) ''
       # Make this configuration the current configuration.
@@ -101,7 +110,7 @@ in
     system.activationScripts = mkOption {
       default = {};
 
-      example = literalExample ''
+      example = literalExpression ''
         { stdio.text =
           '''
             # Needed by some programs.
@@ -138,7 +147,7 @@ in
     system.userActivationScripts = mkOption {
       default = {};
 
-      example = literalExample ''
+      example = literalExpression ''
         { plasmaSetup = {
             text = '''
               ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
@@ -184,9 +193,8 @@ in
 
     environment.usrbinenv = mkOption {
       default = "${pkgs.coreutils}/bin/env";
-      example = literalExample ''
-        "''${pkgs.busybox}/bin/env"
-      '';
+      defaultText = literalExpression ''"''${pkgs.coreutils}/bin/env"'';
+      example = literalExpression ''"''${pkgs.busybox}/bin/env"'';
       type = types.nullOr types.path;
       visible = false;
       description = ''
@@ -254,6 +262,7 @@ in
         script = config.system.userActivationScripts.script;
         unitConfig.ConditionUser = "!@system";
         serviceConfig.Type = "oneshot";
+        wantedBy = [ "default.target" ];
       };
     };
   };
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index b7a062755296b..e105502cf3a48 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -2,6 +2,7 @@
 
 use strict;
 use warnings;
+use File::Path qw(make_path);
 use File::Basename;
 use File::Slurp;
 use Net::DBus;
@@ -10,13 +11,23 @@ use Cwd 'abs_path';
 
 my $out = "@out@";
 
-# FIXME: maybe we should use /proc/1/exe to get the current systemd.
 my $curSystemd = abs_path("/run/current-system/sw/bin");
 
 # To be robust against interruption, record what units need to be started etc.
-my $startListFile = "/run/systemd/start-list";
-my $restartListFile = "/run/systemd/restart-list";
-my $reloadListFile = "/run/systemd/reload-list";
+my $startListFile = "/run/nixos/start-list";
+my $restartListFile = "/run/nixos/restart-list";
+my $reloadListFile = "/run/nixos/reload-list";
+
+# Parse restart/reload requests by the activation script.
+# Activation scripts may write newline-separated units to this
+# file and switch-to-configuration will handle them. While
+# `stopIfChanged = true` is ignored, switch-to-configuration will
+# handle `restartIfChanged = false` and `reloadIfChanged = true`.
+# This also works for socket-activated units.
+my $restartByActivationFile = "/run/nixos/activation-restart-list";
+my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list";
+
+make_path("/run/nixos", { mode => oct(755) });
 
 my $action = shift @ARGV;
 
@@ -138,6 +149,92 @@ sub fingerprintUnit {
     return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : "");
 }
 
+sub handleModifiedUnit {
+    my ($unit, $baseName, $newUnitFile, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_;
+
+    if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.slice$/ || $unit =~ /\.path$/) {
+        # Do nothing.  These cannot be restarted directly.
+        # Slices and Paths don't have to be restarted since
+        # properties (resource limits and inotify watches)
+        # seem to get applied on daemon-reload.
+    } elsif ($unit =~ /\.mount$/) {
+        # Reload the changed mount unit to force a remount.
+        $unitsToReload->{$unit} = 1;
+        recordUnit($reloadListFile, $unit);
+    } else {
+        my $unitInfo = parseUnit($newUnitFile);
+        if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) {
+            $unitsToReload->{$unit} = 1;
+            recordUnit($reloadListFile, $unit);
+        }
+        elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) {
+            $unitsToSkip->{$unit} = 1;
+        } else {
+            # If this unit is socket-activated, then stop it instead
+            # of restarting it to make sure the new version of it is
+            # socket-activated.
+            my $socketActivated = 0;
+            if ($unit =~ /\.service$/) {
+                my @sockets = split / /, ($unitInfo->{Sockets} // "");
+                if (scalar @sockets == 0) {
+                    @sockets = ("$baseName.socket");
+                }
+                foreach my $socket (@sockets) {
+                    if (-e "$out/etc/systemd/system/$socket") {
+                        $socketActivated = 1;
+                        $unitsToStop->{$unit} = 1;
+                        # If the socket was not running previously,
+                        # start it now.
+                        if (not defined $activePrev->{$socket}) {
+                            $unitsToStart->{$socket} = 1;
+                        }
+                    }
+                }
+            }
+
+            # Don't do the rest of this for socket-activated units
+            # because we handled these above where we stop the unit.
+            # Since only services can be socket-activated, the
+            # following condition always evaluates to `true` for
+            # non-service units.
+            if ($socketActivated) {
+                return;
+            }
+
+            # If we are restarting a socket, also stop the corresponding
+            # service. This is required because restarting a socket
+            # when the service is already activated fails.
+            if ($unit =~ /\.socket$/) {
+                my $service = $unitInfo->{Service} // "";
+                if ($service eq "") {
+                    $service = "$baseName.service";
+                }
+                if (defined $activePrev->{$service}) {
+                    $unitsToStop->{$service} = 1;
+                }
+                $unitsToRestart->{$unit} = 1;
+                recordUnit($restartListFile, $unit);
+            } else {
+                # Always restart non-services instead of stopping and starting them
+                # because it doesn't make sense to stop them with a config from
+                # the old evaluation.
+                if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes") || $unit !~ /\.service$/) {
+                    # This unit should be restarted instead of
+                    # stopped and started.
+                    $unitsToRestart->{$unit} = 1;
+                    recordUnit($restartListFile, $unit);
+                } else {
+                    # We write to a file to ensure that the
+                    # service gets restarted if we're interrupted.
+                    $unitsToStart->{$unit} = 1;
+                    recordUnit($startListFile, $unit);
+                    $unitsToStop->{$unit} = 1;
+                }
+            }
+        }
+    }
+}
+
 # Figure out what units need to be stopped, started, restarted or reloaded.
 my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload);
 
@@ -150,7 +247,7 @@ $unitsToRestart{$_} = 1 foreach
     split('\n', read_file($restartListFile, err_mode => 'quiet') // "");
 
 $unitsToReload{$_} = 1 foreach
-    split '\n', read_file($reloadListFile, err_mode => 'quiet') // "";
+    split('\n', read_file($reloadListFile, err_mode => 'quiet') // "");
 
 my $activePrev = getActiveUnits;
 while (my ($unit, $state) = each %{$activePrev}) {
@@ -210,65 +307,7 @@ while (my ($unit, $state) = each %{$activePrev}) {
         }
 
         elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) {
-            if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") {
-                # Do nothing.  These cannot be restarted directly.
-            } elsif ($unit =~ /\.mount$/) {
-                # Reload the changed mount unit to force a remount.
-                $unitsToReload{$unit} = 1;
-                recordUnit($reloadListFile, $unit);
-            } elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/ || $unit =~ /\.slice$/) {
-                # FIXME: do something?
-            } else {
-                my $unitInfo = parseUnit($newUnitFile);
-                if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) {
-                    $unitsToReload{$unit} = 1;
-                    recordUnit($reloadListFile, $unit);
-                }
-                elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) {
-                    $unitsToSkip{$unit} = 1;
-                } else {
-                    if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) {
-                        # This unit should be restarted instead of
-                        # stopped and started.
-                        $unitsToRestart{$unit} = 1;
-                        recordUnit($restartListFile, $unit);
-                    } else {
-                        # If this unit is socket-activated, then stop the
-                        # socket unit(s) as well, and restart the
-                        # socket(s) instead of the service.
-                        my $socketActivated = 0;
-                        if ($unit =~ /\.service$/) {
-                            my @sockets = split / /, ($unitInfo->{Sockets} // "");
-                            if (scalar @sockets == 0) {
-                                @sockets = ("$baseName.socket");
-                            }
-                            foreach my $socket (@sockets) {
-                                if (defined $activePrev->{$socket}) {
-                                    $unitsToStop{$socket} = 1;
-                                    # Only restart sockets that actually
-                                    # exist in new configuration:
-                                    if (-e "$out/etc/systemd/system/$socket") {
-                                        $unitsToStart{$socket} = 1;
-                                        recordUnit($startListFile, $socket);
-                                        $socketActivated = 1;
-                                    }
-                                }
-                            }
-                        }
-
-                        # If the unit is not socket-activated, record
-                        # that this unit needs to be started below.
-                        # We write this to a file to ensure that the
-                        # service gets restarted if we're interrupted.
-                        if (!$socketActivated) {
-                            $unitsToStart{$unit} = 1;
-                            recordUnit($startListFile, $unit);
-                        }
-
-                        $unitsToStop{$unit} = 1;
-                    }
-                }
-            }
+            handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToSkip);
         }
     }
 }
@@ -353,8 +392,6 @@ sub filterUnits {
 }
 
 my @unitsToStopFiltered = filterUnits(\%unitsToStop);
-my @unitsToStartFiltered = filterUnits(\%unitsToStart);
-
 
 # Show dry-run actions.
 if ($action eq "dry-activate") {
@@ -366,13 +403,44 @@ if ($action eq "dry-activate") {
     print STDERR "would activate the configuration...\n";
     system("$out/dry-activate", "$out");
 
+    # Handle the activation script requesting the restart or reload of a unit.
+    my %unitsToAlsoStop;
+    my %unitsToAlsoSkip;
+    foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) {
+        my $unit = $_;
+        my $baseUnit = $unit;
+        my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+
+        # Detect template instances.
+        if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
+          $baseUnit = "$1\@.$2";
+          $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+        }
+
+        my $baseName = $baseUnit;
+        $baseName =~ s/\.[a-z]*$//;
+
+        handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToAlsoStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToAlsoSkip);
+    }
+    unlink($dryRestartByActivationFile);
+
+    my @unitsToAlsoStopFiltered = filterUnits(\%unitsToAlsoStop);
+    if (scalar(keys %unitsToAlsoStop) > 0) {
+        print STDERR "would stop the following units as well: ", join(", ", @unitsToAlsoStopFiltered), "\n"
+            if scalar @unitsToAlsoStopFiltered;
+    }
+
+    print STDERR "would NOT restart the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n"
+        if scalar(keys %unitsToAlsoSkip) > 0;
+
     print STDERR "would restart systemd\n" if $restartSystemd;
+    print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n"
+        if scalar(keys %unitsToReload) > 0;
     print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"
         if scalar(keys %unitsToRestart) > 0;
+    my @unitsToStartFiltered = filterUnits(\%unitsToStart);
     print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n"
         if scalar @unitsToStartFiltered;
-    print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n"
-        if scalar(keys %unitsToReload) > 0;
     exit 0;
 }
 
@@ -383,7 +451,7 @@ if (scalar (keys %unitsToStop) > 0) {
     print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n"
         if scalar @unitsToStopFiltered;
     # Use current version of systemctl binary before daemon is reexeced.
-    system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors?
+    system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop));
 }
 
 print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
@@ -395,6 +463,41 @@ my $res = 0;
 print STDERR "activating the configuration...\n";
 system("$out/activate", "$out") == 0 or $res = 2;
 
+# Handle the activation script requesting the restart or reload of a unit.
+# We can only restart and reload (not stop/start) because the units to be
+# stopped are already stopped before the activation script is run. We do however
+# make an exception for services that are socket-activated and that have to be stopped
+# instead of being restarted.
+my %unitsToAlsoStop;
+my %unitsToAlsoSkip;
+foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) {
+    my $unit = $_;
+    my $baseUnit = $unit;
+    my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+
+    # Detect template instances.
+    if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
+      $baseUnit = "$1\@.$2";
+      $newUnitFile = "$out/etc/systemd/system/$baseUnit";
+    }
+
+    my $baseName = $baseUnit;
+    $baseName =~ s/\.[a-z]*$//;
+
+    handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToAlsoStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToAlsoSkip);
+}
+unlink($restartByActivationFile);
+
+my @unitsToAlsoStopFiltered = filterUnits(\%unitsToAlsoStop);
+if (scalar(keys %unitsToAlsoStop) > 0) {
+    print STDERR "stopping the following units as well: ", join(", ", @unitsToAlsoStopFiltered), "\n"
+        if scalar @unitsToAlsoStopFiltered;
+    system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToAlsoStop));
+}
+
+print STDERR "NOT restarting the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n"
+    if scalar(keys %unitsToAlsoSkip) > 0;
+
 # Restart systemd if necessary. Note that this is done using the
 # current version of systemd, just in case the new one has trouble
 # communicating with the running pid 1.
@@ -440,8 +543,36 @@ if (scalar(keys %unitsToReload) > 0) {
 # than stopped and started).
 if (scalar(keys %unitsToRestart) > 0) {
     print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n";
-    system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4;
+
+    # We split the units to be restarted into sockets and non-sockets.
+    # This is because restarting sockets may fail which is not bad by
+    # itself but which will prevent changes on the sockets. We usually
+    # restart the socket and stop the service before that. Restarting
+    # the socket will fail however when the service was re-activated
+    # in the meantime. There is no proper way to prevent that from happening.
+    my @unitsWithErrorHandling = grep { $_ !~ /\.socket$/ } sort(keys %unitsToRestart);
+    my @unitsWithoutErrorHandling = grep { $_ =~ /\.socket$/ } sort(keys %unitsToRestart);
+
+    if (scalar(@unitsWithErrorHandling) > 0) {
+        system("@systemd@/bin/systemctl", "restart", "--", @unitsWithErrorHandling) == 0 or $res = 4;
+    }
+    if (scalar(@unitsWithoutErrorHandling) > 0) {
+        # Don't print warnings from systemctl
+        no warnings 'once';
+        open(OLDERR, ">&", \*STDERR);
+        close(STDERR);
+
+        my $ret = system("@systemd@/bin/systemctl", "restart", "--", @unitsWithoutErrorHandling);
+
+        # Print stderr again
+        open(STDERR, ">&OLDERR");
+
+        if ($ret ne 0) {
+            print STDERR "warning: some sockets failed to restart. Please check your journal (journalctl -eb) and act accordingly.\n";
+        }
+    }
     unlink($restartListFile);
+    unlink($restartByActivationFile);
 }
 
 # Start all active targets, as well as changed units we stopped above.
@@ -450,6 +581,7 @@ if (scalar(keys %unitsToRestart) > 0) {
 # that are symlinks to other units.  We shouldn't start both at the
 # same time because we'll get a "Failed to add path to set" error from
 # systemd.
+my @unitsToStartFiltered = filterUnits(\%unitsToStart);
 print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n"
     if scalar @unitsToStartFiltered;
 system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4;
@@ -457,7 +589,7 @@ unlink($startListFile);
 
 
 # Print failed and new units.
-my (@failed, @new, @restarting);
+my (@failed, @new);
 my $activeNew = getActiveUnits;
 while (my ($unit, $state) = each %{$activeNew}) {
     if ($state->{state} eq "failed") {
@@ -473,7 +605,9 @@ while (my ($unit, $state) = each %{$activeNew}) {
             push @failed, $unit;
         }
     }
-    elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit}) {
+    # Ignore scopes since they are not managed by this script but rather
+    # created and managed by third-party services via the systemd dbus API.
+    elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit} && $unit !~ /\.scope$/) {
         push @new, $unit;
     }
 }
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 616e1422aa8cd..68da910d29cc8 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -61,8 +61,8 @@ let
       substituteInPlace $out/dry-activate --subst-var out
       chmod u+x $out/activate $out/dry-activate
       unset activationScript dryActivationScript
-      ${pkgs.runtimeShell} -n $out/activate
-      ${pkgs.runtimeShell} -n $out/dry-activate
+      ${pkgs.stdenv.shell} -n $out/activate
+      ${pkgs.stdenv.shell} -n $out/dry-activate
 
       cp ${config.system.build.bootStage2} $out/init
       substituteInPlace $out/init --subst-var-by systemConfig $out
@@ -84,6 +84,13 @@ let
       export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
       substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
       chmod +x $out/bin/switch-to-configuration
+      ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
+        if ! output=$($perl/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
+          echo "switch-to-configuration syntax is not valid:"
+          echo "$output"
+          exit 1
+        fi
+      ''}
 
       echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies
 
@@ -155,7 +162,7 @@ in
 
     specialisation = mkOption {
       default = {};
-      example = lib.literalExample "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }";
+      example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }";
       description = ''
         Additional configurations to build. If
         <literal>inheritParentConfig</literal> is true, the system
@@ -243,7 +250,7 @@ in
 
     system.replaceRuntimeDependencies = mkOption {
       default = [];
-      example = lib.literalExample "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]";
+      example = lib.literalExpression "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]";
       type = types.listOf (types.submodule (
         { ... }: {
           options.original = mkOption {
@@ -274,7 +281,11 @@ in
         if config.networking.hostName == ""
         then "unnamed"
         else config.networking.hostName;
-      defaultText = '''networking.hostName' if non empty else "unnamed"'';
+      defaultText = literalExpression ''
+        if config.networking.hostName == ""
+        then "unnamed"
+        else config.networking.hostName;
+      '';
       description = ''
         The name of the system used in the <option>system.build.toplevel</option> derivation.
         </para><para>
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 2408ecc80d22b..fdb4d0e4c7fb3 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -248,6 +248,7 @@ in {
         description = ''
           List of systems to emulate. Will also configure Nix to
           support your new systems.
+          Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
         '';
         type = types.listOf types.str;
       };
diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix
index b35fb0b57c059..9b52d4bbdb1ee 100644
--- a/nixos/modules/system/boot/initrd-openvpn.nix
+++ b/nixos/modules/system/boot/initrd-openvpn.nix
@@ -35,7 +35,7 @@ in
           </para>
         </warning>
       '';
-      example = "./configuration.ovpn";
+      example = literalExpression "./configuration.ovpn";
     };
 
   };
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 00ac83a189724..0999142de86ea 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -78,7 +78,7 @@ in
     authorizedKeys = mkOption {
       type = types.listOf types.str;
       default = config.users.users.root.openssh.authorizedKeys.keys;
-      defaultText = "config.users.users.root.openssh.authorizedKeys.keys";
+      defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys";
       description = ''
         Authorized keys for the root user on initrd.
       '';
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 1a6a9d99d5bbe..d147155d796c1 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -23,7 +23,7 @@ in
 
     boot.kernel.features = mkOption {
       default = {};
-      example = literalExample "{ debug = true; }";
+      example = literalExpression "{ debug = true; }";
       internal = true;
       description = ''
         This option allows to enable or disable certain kernel features.
@@ -46,8 +46,8 @@ in
       });
       # We don't want to evaluate all of linuxPackages for the manual
       # - some of it might not even evaluate correctly.
-      defaultText = "pkgs.linuxPackages";
-      example = literalExample "pkgs.linuxPackages_2_6_25";
+      defaultText = literalExpression "pkgs.linuxPackages";
+      example = literalExpression "pkgs.linuxKernel.packages.linux_5_10";
       description = ''
         This option allows you to override the Linux kernel used by
         NixOS.  Since things like external kernel module packages are
@@ -65,7 +65,7 @@ in
     boot.kernelPatches = mkOption {
       type = types.listOf types.attrs;
       default = [];
-      example = literalExample "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
+      example = literalExpression "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
       description = "A list of additional patches to apply to the kernel.";
     };
 
@@ -83,7 +83,10 @@ in
     };
 
     boot.kernelParams = mkOption {
-      type = types.listOf types.str;
+      type = types.listOf (types.strMatching ''([^"[:space:]]|"[^"]*")+'' // {
+        name = "kernelParam";
+        description = "string, with spaces inside double quotes";
+      });
       default = [ ];
       description = "Parameters added to the kernel command line.";
     };
@@ -113,7 +116,7 @@ in
     boot.extraModulePackages = mkOption {
       type = types.listOf types.package;
       default = [];
-      example = literalExample "[ config.boot.kernelPackages.nvidia_x11 ]";
+      example = literalExpression "[ config.boot.kernelPackages.nvidia_x11 ]";
       description = "A list of additional packages supplying kernel modules.";
     };
 
@@ -181,7 +184,7 @@ in
 
     system.requiredKernelConfig = mkOption {
       default = [];
-      example = literalExample ''
+      example = literalExpression ''
         with config.lib.kernelConfig; [
           (isYes "MODULES")
           (isEnabled "FB_CON_DECOR")
@@ -240,7 +243,7 @@ in
             "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat"
             "hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft"
 
-          ] ++ optionals (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
+          ] ++ optionals pkgs.stdenv.hostPlatform.isx86 [
             # Misc. x86 keyboard stuff.
             "pcips2" "atkbd" "i8042"
 
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
index 5d9534024b06b..495fe74bc21ee 100644
--- a/nixos/modules/system/boot/kernel_config.nix
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -100,7 +100,7 @@ in
 
     settings = mkOption {
       type = types.attrsOf kernelItem;
-      example = literalExample '' with lib.kernel; {
+      example = literalExpression '' with lib.kernel; {
         "9P_NET" = yes;
         USB = option yes;
         MMC_BLOCK_MINORS = freeform "32";
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 1be663670384c..fa8500dd42bdc 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -329,7 +329,7 @@ in
 
       extraInstallCommands = mkOption {
         default = "";
-        example = literalExample ''
+        example = ''
           # the example below generates detached signatures that GRUB can verify
           # https://www.gnu.org/software/grub/manual/grub/grub.html#Using-digital-signatures
           ''${pkgs.findutils}/bin/find /boot -not -path "/boot/efi/*" -type f -name '*.sig' -delete
@@ -392,7 +392,7 @@ in
       extraFiles = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        example = literalExample ''
+        example = literalExpression ''
           { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; }
         '';
         description = ''
@@ -413,7 +413,7 @@ in
 
       splashImage = mkOption {
         type = types.nullOr types.path;
-        example = literalExample "./my-background.png";
+        example = literalExpression "./my-background.png";
         description = ''
           Background image used for GRUB.
           Set to <literal>null</literal> to run GRUB in text mode.
@@ -449,7 +449,7 @@ in
 
       theme = mkOption {
         type = types.nullOr types.path;
-        example = literalExample "pkgs.nixos-grub2-theme";
+        example = literalExpression "pkgs.nixos-grub2-theme";
         default = null;
         description = ''
           Grub theme to be used.
@@ -475,7 +475,7 @@ in
       font = mkOption {
         type = types.nullOr types.path;
         default = "${realGrub}/share/grub/unicode.pf2";
-        defaultText = ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
+        defaultText = literalExpression ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
         description = ''
           Path to a TrueType, OpenType, or pf2 font to be used by Grub.
         '';
@@ -483,7 +483,7 @@ in
 
       fontSize = mkOption {
         type = types.nullOr types.int;
-        example = literalExample 16;
+        example = 16;
         default = null;
         description = ''
           Font size for the grub menu. Ignored unless <literal>font</literal>
diff --git a/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixos/modules/system/boot/loader/grub/ipxe.nix
index 249c2761934d1..ef8595592f412 100644
--- a/nixos/modules/system/boot/loader/grub/ipxe.nix
+++ b/nixos/modules/system/boot/loader/grub/ipxe.nix
@@ -33,7 +33,7 @@ in
             booting from the GRUB boot menu.
           '';
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           { demo = '''
               #!ipxe
               dhcp
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 7134b4321630e..c38bef9d6d4b7 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -17,19 +17,28 @@ import glob
 import os.path
 from typing import Tuple, List, Optional
 
+SystemIdentifier = Tuple[Optional[str], int, Optional[str]]
+
 
 def copy_if_not_exists(source: str, dest: str) -> None:
     if not os.path.exists(dest):
         shutil.copyfile(source, dest)
 
 
-def system_dir(profile: Optional[str], generation: int) -> str:
+def generation_dir(profile: Optional[str], generation: int) -> str:
     if profile:
         return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
     else:
         return "/nix/var/nix/profiles/system-%d-link" % (generation)
 
-BOOT_ENTRY = """title NixOS{profile}
+def system_dir(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+    d = generation_dir(profile, generation)
+    if specialisation:
+        return os.path.join(d, "specialisation", specialisation)
+    else:
+        return d
+
+BOOT_ENTRY = """title NixOS{profile}{specialisation}
 version Generation {generation} {description}
 linux {kernel}
 initrd {initrd}
@@ -46,26 +55,34 @@ efi /efi/memtest86/BOOTX64.efi
 """
 
 
-def write_loader_conf(profile: Optional[str], generation: int) -> None:
+def generation_conf_filename(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+    pieces = [
+        "nixos",
+        profile or None,
+        "generation",
+        str(generation),
+        f"specialisation-{specialisation}" if specialisation else None,
+    ]
+    return "-".join(p for p in pieces if p) + ".conf"
+
+
+def write_loader_conf(profile: Optional[str], generation: int, specialisation: Optional[str]) -> None:
     with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
         if "@timeout@" != "":
             f.write("timeout @timeout@\n")
-        if profile:
-            f.write("default nixos-%s-generation-%d.conf\n" % (profile, generation))
-        else:
-            f.write("default nixos-generation-%d.conf\n" % (generation))
+        f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
         if not @editor@:
             f.write("editor 0\n");
         f.write("console-mode @consoleMode@\n");
     os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
 
 
-def profile_path(profile: Optional[str], generation: int, name: str) -> str:
-    return os.path.realpath("%s/%s" % (system_dir(profile, generation), name))
+def profile_path(profile: Optional[str], generation: int, specialisation: Optional[str], name: str) -> str:
+    return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name))
 
 
-def copy_from_profile(profile: Optional[str], generation: int, name: str, dry_run: bool = False) -> str:
-    store_file_path = profile_path(profile, generation, name)
+def copy_from_profile(profile: Optional[str], generation: int, specialisation: Optional[str], name: str, dry_run: bool = False) -> str:
+    store_file_path = profile_path(profile, generation, specialisation, name)
     suffix = os.path.basename(store_file_path)
     store_dir = os.path.basename(os.path.dirname(store_file_path))
     efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
@@ -95,19 +112,17 @@ def describe_generation(generation_dir: str) -> str:
     return description
 
 
-def write_entry(profile: Optional[str], generation: int, machine_id: str) -> None:
-    kernel = copy_from_profile(profile, generation, "kernel")
-    initrd = copy_from_profile(profile, generation, "initrd")
+def write_entry(profile: Optional[str], generation: int, specialisation: Optional[str], machine_id: str) -> None:
+    kernel = copy_from_profile(profile, generation, specialisation, "kernel")
+    initrd = copy_from_profile(profile, generation, specialisation, "initrd")
     try:
-        append_initrd_secrets = profile_path(profile, generation, "append-initrd-secrets")
+        append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
         subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
     except FileNotFoundError:
         pass
-    if profile:
-        entry_file = "@efiSysMountPoint@/loader/entries/nixos-%s-generation-%d.conf" % (profile, generation)
-    else:
-        entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation)
-    generation_dir = os.readlink(system_dir(profile, generation))
+    entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
+        generation_conf_filename(profile, generation, specialisation))
+    generation_dir = os.readlink(system_dir(profile, generation, specialisation))
     tmp_path = "%s.tmp" % (entry_file)
     kernel_params = "init=%s/init " % generation_dir
 
@@ -115,6 +130,7 @@ def write_entry(profile: Optional[str], generation: int, machine_id: str) -> Non
         kernel_params = kernel_params + params_file.read()
     with open(tmp_path, 'w') as f:
         f.write(BOOT_ENTRY.format(profile=" [" + profile + "]" if profile else "",
+                    specialisation=" (%s)" % specialisation if specialisation else "",
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
@@ -133,7 +149,7 @@ def mkdir_p(path: str) -> None:
             raise
 
 
-def get_generations(profile: Optional[str] = None) -> List[Tuple[Optional[str], int]]:
+def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
     gen_list = subprocess.check_output([
         "@nix@/bin/nix-env",
         "--list-generations",
@@ -145,10 +161,19 @@ def get_generations(profile: Optional[str] = None) -> List[Tuple[Optional[str],
     gen_lines.pop()
 
     configurationLimit = @configurationLimit@
-    return [ (profile, int(line.split()[0])) for line in gen_lines ][-configurationLimit:]
+    configurations: List[SystemIdentifier] = [ (profile, int(line.split()[0]), None) for line in gen_lines ]
+    return configurations[-configurationLimit:]
 
 
-def remove_old_entries(gens: List[Tuple[Optional[str], int]]) -> None:
+def get_specialisations(profile: Optional[str], generation: int, _: Optional[str]) -> List[SystemIdentifier]:
+    specialisations_dir = os.path.join(
+            system_dir(profile, generation, None), "specialisation")
+    if not os.path.exists(specialisations_dir):
+        return []
+    return [(profile, generation, spec) for spec in os.listdir(specialisations_dir)]
+
+
+def remove_old_entries(gens: List[SystemIdentifier]) -> None:
     rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
     rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-(.*)\.conf$")
     known_paths = []
@@ -208,10 +233,15 @@ def main() -> None:
         if os.path.exists("@efiSysMountPoint@/loader/loader.conf"):
             os.unlink("@efiSysMountPoint@/loader/loader.conf")
 
-        if "@canTouchEfiVariables@" == "1":
-            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "install"])
-        else:
-            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "--no-variables", "install"])
+        flags = []
+
+        if "@canTouchEfiVariables@" != "1":
+            flags.append("--no-variables")
+
+        if "@graceful@" == "1":
+            flags.append("--graceful")
+
+        subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@"] + flags + ["install"])
     else:
         # Update bootloader to latest if needed
         systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[1]
@@ -238,6 +268,8 @@ def main() -> None:
     for gen in gens:
         try:
             write_entry(*gen, machine_id)
+            for specialisation in get_specialisations(*gen):
+                write_entry(*specialisation, machine_id)
             if os.readlink(system_dir(*gen)) == args.default_config:
                 write_loader_conf(*gen)
         except OSError as e:
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 ff304f570d356..0f76d7d6b24a2 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -24,7 +24,7 @@ let
 
     configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
 
-    inherit (cfg) consoleMode;
+    inherit (cfg) consoleMode graceful;
 
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
 
@@ -126,6 +126,22 @@ in {
         '';
       };
     };
+
+    graceful = mkOption {
+      default = false;
+
+      type = types.bool;
+
+      description = ''
+        Invoke <literal>bootctl install</literal> with the <literal>--graceful</literal> option,
+        which ignores errors when EFI variables cannot be written or when the EFI System Partition
+        cannot be found. Currently only applies to random seed operations.
+
+        Only enable this option if <literal>systemd-boot</literal> otherwise fails to install, as the
+        scope or implication of the <literal>--graceful</literal> option may change in the future.
+      '';
+    };
+
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index f87d3b07a3601..f0d3170dc5ac2 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -332,6 +332,7 @@ let
 
         if [ $? == 0 ]; then
             echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path}
+            sync /crypt-storage${dev.yubikey.storage.path}
         else
             echo "Warning: Could not update LUKS key, current challenge persists!"
         fi
@@ -663,13 +664,11 @@ in
                 };
 
                 encryptedPass = mkOption {
-                  default = "";
                   type = types.path;
                   description = "Path to the GPG encrypted passphrase.";
                 };
 
                 publicKey = mkOption {
-                  default = "";
                   type = types.path;
                   description = "Path to the Public Key.";
                 };
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 1de58b3d2c4a8..2e17bdf6bb659 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -131,6 +131,7 @@ let
           "fou"
           "xfrm"
           "ifb"
+          "batadv"
         ])
         (assertByteFormat "MTUBytes")
         (assertMacAddress "MACAddress")
@@ -250,6 +251,16 @@ let
         (assertRange "ERSPANIndex" 1 1048575)
       ];
 
+      sectionFooOverUDP = checkUnitConfig "FooOverUDP" [
+        (assertOnlyFields [
+          "Port"
+          "Encapsulation"
+          "Protocol"
+        ])
+        (assertPort "Port")
+        (assertValueOneOf "Encapsulation" ["FooOverUDP" "GenericUDPEncapsulation"])
+      ];
+
       sectionPeer = checkUnitConfig "Peer" [
         (assertOnlyFields [
           "Name"
@@ -371,6 +382,29 @@ let
         (assertInt "Table")
         (assertMinimum "Table" 0)
       ];
+
+      sectionBatmanAdvanced = checkUnitConfig "BatmanAdvanced" [
+        (assertOnlyFields [
+          "GatewayMode"
+          "Aggregation"
+          "BridgeLoopAvoidance"
+          "DistributedArpTable"
+          "Fragmentation"
+          "HopPenalty"
+          "OriginatorIntervalSec"
+          "GatewayBandwithDown"
+          "GatewayBandwithUp"
+          "RoutingAlgorithm"
+        ])
+        (assertValueOneOf "GatewayMode" ["off" "client" "server"])
+        (assertValueOneOf "Aggregation" boolValues)
+        (assertValueOneOf "BridgeLoopAvoidance" boolValues)
+        (assertValueOneOf "DistributedArpTable" boolValues)
+        (assertValueOneOf "Fragmentation" boolValues)
+        (assertInt "HopPenalty")
+        (assertRange "HopPenalty" 0 255)
+        (assertValueOneOf "RoutingAlgorithm" ["batman-v" "batman-iv"])
+      ];
     };
 
     network = {
@@ -384,6 +418,7 @@ let
           "AllMulticast"
           "Unmanaged"
           "RequiredForOnline"
+          "ActivationPolicy"
         ])
         (assertMacAddress "MACAddress")
         (assertByteFormat "MTUBytes")
@@ -402,6 +437,14 @@ let
           "enslaved"
           "routable"
         ]))
+        (assertValueOneOf "ActivationPolicy" ([
+          "up"
+          "always-up"
+          "manual"
+          "always-down"
+          "down"
+          "bound"
+        ]))
       ];
 
       sectionNetwork = checkUnitConfig "Network" [
@@ -454,6 +497,7 @@ let
           "IgnoreCarrierLoss"
           "Xfrm"
           "KeepConfiguration"
+          "BatmanAdvanced"
         ])
         # Note: For DHCP the values both, none, v4, v6 are deprecated
         (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"])
@@ -659,6 +703,9 @@ let
           "SendOption"
           "UserClass"
           "VendorClass"
+          "DUIDType"
+          "DUIDRawData"
+          "IAID"
         ])
         (assertValueOneOf "UseAddress" boolValues)
         (assertValueOneOf "UseDNS" boolValues)
@@ -668,6 +715,7 @@ let
         (assertValueOneOf "ForceDHCPv6PDOtherInformation" boolValues)
         (assertValueOneOf "WithoutRA" ["solicit" "information-request"])
         (assertRange "SendOption" 1 65536)
+        (assertInt "IAID")
       ];
 
       sectionDHCPv6PrefixDelegation = checkUnitConfig "DHCPv6PrefixDelegation" [
@@ -835,7 +883,6 @@ let
     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
@@ -850,7 +897,6 @@ let
   netdevOptions = commonNetworkOptions // {
 
     netdevConfig = mkOption {
-      default = {};
       example = { Name = "mybridge"; Kind = "bridge"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionNetdev;
       description = ''
@@ -887,7 +933,6 @@ let
 
     vxlanConfig = mkOption {
       default = {};
-      example = { Id = "4"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVXLAN;
       description = ''
         Each attribute in this set specifies an option in the
@@ -909,6 +954,18 @@ let
       '';
     };
 
+    fooOverUDPConfig = mkOption {
+      default = { };
+      example = { Port = 9001; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionFooOverUDP;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[FooOverUDP]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
     peerConfig = mkOption {
       default = {};
       example = { Name = "veth2"; };
@@ -950,7 +1007,7 @@ let
       example = {
         PrivateKeyFile = "/etc/wireguard/secret.key";
         ListenPort = 51820;
-        FwMark = 42;
+        FirewallMark = 42;
       };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuard;
       description = ''
@@ -1024,12 +1081,26 @@ let
       '';
     };
 
+    batmanAdvancedConfig = mkOption {
+      default = {};
+      example = {
+        GatewayMode = "server";
+        RoutingAlgorithm = "batman-v";
+      };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBatmanAdvanced;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[BatmanAdvanced]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
   };
 
   addressOptions = {
     options = {
       addressConfig = mkOption {
-        default = {};
         example = { Address = "192.168.0.100/24"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionAddress;
         description = ''
@@ -1046,7 +1117,7 @@ let
     options = {
       routingPolicyRuleConfig = mkOption {
         default = { };
-        example = { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; } ;};
+        example = { Table = 10; IncomingInterface = "eth1"; Family = "both"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoutingPolicyRule;
         description = ''
           Each attribute in this set specifies an option in the
@@ -1137,7 +1208,7 @@ let
 
     dhcpV6Config = mkOption {
       default = {};
-      example = { UseDNS = true; UseRoutes = true; };
+      example = { UseDNS = true; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6;
       description = ''
         Each attribute in this set specifies an option in the
@@ -1204,7 +1275,7 @@ let
 
     ipv6Prefixes = mkOption {
       default = [];
-      example = { AddressAutoconfiguration = true; OnLink = true; };
+      example = [ { AddressAutoconfiguration = true; OnLink = true; } ];
       type = with types; listOf (submodule ipv6PrefixOptions);
       description = ''
         A list of ipv6Prefix sections to be added to the unit.  See
@@ -1440,6 +1511,10 @@ let
           [Tunnel]
           ${attrsToSection def.tunnelConfig}
         ''
+        + optionalString (def.fooOverUDPConfig != { }) ''
+          [FooOverUDP]
+          ${attrsToSection def.fooOverUDPConfig}
+        ''
         + optionalString (def.peerConfig != { }) ''
           [Peer]
           ${attrsToSection def.peerConfig}
@@ -1472,6 +1547,10 @@ let
           [VRF]
           ${attrsToSection def.vrfConfig}
         ''
+        + optionalString (def.batmanAdvancedConfig != { }) ''
+          [BatmanAdvanced]
+          ${attrsToSection def.batmanAdvancedConfig}
+        ''
         + def.extraConfig;
     };
 
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index 2a545e5525135..4b8194d2f85c1 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -62,6 +62,7 @@ in
 
       font = mkOption {
         default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf";
+        defaultText = literalExpression ''"''${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"'';
         type = types.path;
         description = ''
           Font file made available for displaying text on the splash screen.
@@ -88,7 +89,7 @@ in
         type = types.path;
         # Dimensions are 48x48 to match GDM logo
         default = "${nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png";
-        defaultText = ''pkgs.fetchurl {
+        defaultText = literalExpression ''pkgs.fetchurl {
           url = "https://nixos.org/logo/nixos-hires.png";
           sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
         }'';
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 03133fa1bc43d..adbed9d8d58e7 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -137,6 +137,14 @@ let
         copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
       ''}
 
+      # Copy multipath.
+      ${optionalString config.services.multipath.enable ''
+        copy_bin_and_libs ${config.services.multipath.package}/bin/multipath
+        copy_bin_and_libs ${config.services.multipath.package}/bin/multipathd
+        # Copy lib/multipath manually.
+        cp -rpv ${config.services.multipath.package}/lib/multipath $out/lib
+      ''}
+
       # Copy secrets if needed.
       #
       # TODO: move out to a separate script; see #85000.
@@ -199,6 +207,10 @@ let
       $out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
       LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM"
       $out/bin/mdadm --version
+      ${optionalString config.services.multipath.enable ''
+        ($out/bin/multipath || true) 2>&1 | grep -q 'need to be root'
+        ($out/bin/multipathd || true) 2>&1 | grep -q 'need to be root'
+      ''}
 
       ${config.boot.initrd.extraUtilsCommandsTest}
       fi
@@ -338,7 +350,26 @@ let
         { object = pkgs.kmod-debian-aliases;
           symlink = "/etc/modprobe.d/debian.conf";
         }
-      ];
+      ] ++ lib.optionals config.services.multipath.enable [
+        { object = pkgs.runCommand "multipath.conf" {
+              src = config.environment.etc."multipath.conf".text;
+              preferLocalBuild = true;
+            } ''
+              target=$out
+              printf "$src" > $out
+              substituteInPlace $out \
+                --replace ${config.services.multipath.package}/lib ${extraUtils}/lib
+            '';
+          symlink = "/etc/multipath.conf";
+        }
+      ] ++ (lib.mapAttrsToList
+        (symlink: options:
+          {
+            inherit symlink;
+            object = options.source;
+          }
+        )
+        config.boot.initrd.extraFiles);
   };
 
   # Script to add secret files to the initrd at bootloader update time
@@ -411,7 +442,7 @@ in
     boot.initrd.enable = mkOption {
       type = types.bool;
       default = !config.boot.isContainer;
-      defaultText = "!config.boot.isContainer";
+      defaultText = literalExpression "!config.boot.isContainer";
       description = ''
         Whether to enable the NixOS initial RAM disk (initrd). This may be
         needed to perform some initialisation tasks (like mounting
@@ -419,6 +450,22 @@ in
       '';
     };
 
+    boot.initrd.extraFiles = mkOption {
+      default = { };
+      type = types.attrsOf
+        (types.submodule {
+          options = {
+            source = mkOption {
+              type = types.package;
+              description = "The object to make available inside the initrd.";
+            };
+          };
+        });
+      description = ''
+        Extra files to link and copy in to the initrd.
+      '';
+    };
+
     boot.initrd.prepend = mkOption {
       default = [ ];
       type = types.listOf types.str;
@@ -527,7 +574,7 @@ in
         then "zstd"
         else "gzip"
       );
-      defaultText = "zstd if the kernel supports it (5.9+), gzip if not.";
+      defaultText = literalDocBook "<literal>zstd</literal> if the kernel supports it (5.9+), <literal>gzip</literal> if not";
       type = types.unspecified; # We don't have a function type...
       description = ''
         The compressor to use on the initrd image. May be any of:
@@ -559,7 +606,7 @@ in
             is the path it should be copied from (or null for the same
             path inside and out).
           '';
-        example = literalExample
+        example = literalExpression
           ''
             { "/etc/dropbear/dropbear_rsa_host_key" =
                 ./secret-dropbear-key;
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 50ee0b8841e50..afaca2e4158d7 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -62,9 +62,11 @@ chown -f 0:30000 /nix/store
 chmod -f 1775 /nix/store
 if [ -n "@readOnlyStore@" ]; then
     if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then
-        # FIXME when linux < 4.5 is EOL, switch to atomic bind mounts
-        #mount /nix/store /nix/store -o bind,remount,ro
-        mount --bind /nix/store /nix/store
+        if [ -z "$container" ]; then
+            mount --bind /nix/store /nix/store
+        else
+            mount --rbind /nix/store /nix/store
+        fi
         mount -o remount,ro,bind /nix/store
     fi
 fi
diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix
index 2dbf15031a088..6c4d27018eed8 100644
--- a/nixos/modules/system/boot/systemd-lib.nix
+++ b/nixos/modules/system/boot/systemd-lib.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.systemd;
-  lndir = "${pkgs.xorg.lndir}/bin/lndir";
+  lndir = "${pkgs.buildPackages.xorg.lndir}/bin/lndir";
 in rec {
 
   shellEscape = s: (replaceChars [ "\\" ] [ "\\\\" ] s);
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 934c57f839189..8fcf62d7fbffd 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -26,6 +26,8 @@ let
       "nss-user-lookup.target"
       "time-sync.target"
       "cryptsetup.target"
+      "cryptsetup-pre.target"
+      "remote-cryptsetup.target"
       "sigpwr.target"
       "timers.target"
       "paths.target"
@@ -40,7 +42,7 @@ let
       "systemd-udevd-kernel.socket"
       "systemd-udevd.service"
       "systemd-udev-settle.service"
-      "systemd-udev-trigger.service"
+      ] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") ++ [
       # hwdb.bin is managed by NixOS
       # "systemd-hwdb-update.service"
 
@@ -426,7 +428,7 @@ in
 
     systemd.package = mkOption {
       default = pkgs.systemd;
-      defaultText = "pkgs.systemd";
+      defaultText = literalExpression "pkgs.systemd";
       type = types.package;
       description = "The systemd package.";
     };
@@ -446,7 +448,7 @@ in
     systemd.packages = mkOption {
       default = [];
       type = types.listOf types.package;
-      example = literalExample "[ pkgs.systemd-cryptsetup-generator ]";
+      example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
       description = "Packages providing systemd units and hooks.";
     };
 
@@ -663,7 +665,7 @@ in
 
     services.journald.forwardToSyslog = mkOption {
       default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
-      defaultText = "services.rsyslogd.enable || services.syslog-ng.enable";
+      defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
       type = types.bool;
       description = ''
         Whether to forward log messages to syslog.
@@ -722,7 +724,7 @@ in
 
     services.logind.lidSwitchExternalPower = mkOption {
       default = config.services.logind.lidSwitch;
-      defaultText = "services.logind.lidSwitch";
+      defaultText = literalExpression "services.logind.lidSwitch";
       example = "ignore";
       type = logindHandlerType;
 
@@ -768,7 +770,7 @@ in
     systemd.tmpfiles.packages = mkOption {
       type = types.listOf types.package;
       default = [];
-      example = literalExample "[ pkgs.lvm2 ]";
+      example = literalExpression "[ pkgs.lvm2 ]";
       apply = map getLib;
       description = ''
         List of packages containing <command>systemd-tmpfiles</command> rules.
@@ -1056,10 +1058,20 @@ in
 
     services.dbus.enable = true;
 
-    users.users.systemd-coredump.uid = config.ids.uids.systemd-coredump;
-    users.users.systemd-network.uid = config.ids.uids.systemd-network;
+    users.users.systemd-coredump = {
+      uid = config.ids.uids.systemd-coredump;
+      group = "systemd-coredump";
+    };
+    users.groups.systemd-coredump = {};
+    users.users.systemd-network = {
+      uid = config.ids.uids.systemd-network;
+      group = "systemd-network";
+    };
     users.groups.systemd-network.gid = config.ids.gids.systemd-network;
-    users.users.systemd-resolve.uid = config.ids.uids.systemd-resolve;
+    users.users.systemd-resolve = {
+      uid = config.ids.uids.systemd-resolve;
+      group = "systemd-resolve";
+    };
     users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
 
     # Target for ‘charon send-keys’ to hook into.
diff --git a/nixos/modules/system/boot/tmp.nix b/nixos/modules/system/boot/tmp.nix
index 5bb299adb15f0..6edafd6695b62 100644
--- a/nixos/modules/system/boot/tmp.nix
+++ b/nixos/modules/system/boot/tmp.nix
@@ -2,6 +2,9 @@
 
 with lib;
 
+let
+  cfg = config.boot;
+in
 {
 
   ###### interface
@@ -24,18 +27,28 @@ with lib;
       '';
     };
 
+    boot.tmpOnTmpfsSize = mkOption {
+      type = types.oneOf [ types.str types.types.ints.positive ];
+      default = "50%";
+      description = ''
+        Size of tmpfs in percentage.
+        Percentage is defined by systemd.
+      '';
+    };
+
   };
 
   ###### implementation
 
   config = {
 
-    systemd.mounts = mkIf config.boot.tmpOnTmpfs [
+    # When changing remember to update /tmp mount in virtualisation/qemu-vm.nix
+    systemd.mounts = mkIf cfg.tmpOnTmpfs [
       {
         what = "tmpfs";
         where = "/tmp";
         type = "tmpfs";
-        mountConfig.Options = [ "mode=1777" "strictatime" "rw" "nosuid" "nodev" "size=50%" ];
+        mountConfig.Options = [ "mode=1777" "strictatime" "rw" "nosuid" "nodev" "size=${toString cfg.tmpOnTmpfsSize}" ];
       }
     ];
 
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index 84468ea31f741..6cc8c341e6dfa 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -6,9 +6,7 @@ with lib;
 
 let
 
-  # if the source is a local file, it should be imported to the store
-  localToStore = mapAttrs (name: value: if name == "source" then "${value}" else value);
-  etc' = map localToStore (filter (f: f.enable) (attrValues config.environment.etc));
+  etc' = filter (f: f.enable) (attrValues config.environment.etc);
 
   etc = pkgs.runCommandLocal "etc" {
     # This is needed for the systemd module
@@ -55,7 +53,8 @@ let
     mkdir -p "$out/etc"
     ${concatMapStringsSep "\n" (etcEntry: escapeShellArgs [
       "makeEtcEntry"
-      etcEntry.source
+      # Force local source paths to be added to the store
+      "${etcEntry.source}"
       etcEntry.target
       etcEntry.mode
       etcEntry.user
@@ -73,7 +72,7 @@ in
 
     environment.etc = mkOption {
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         { example-configuration-file =
             { source = "/nix/store/.../etc/dir/file.conf.example";
               mode = "0440";
@@ -86,7 +85,7 @@ in
       '';
 
       type = with types; attrsOf (submodule (
-        { name, config, ... }:
+        { name, config, options, ... }:
         { options = {
 
             enable = mkOption {
@@ -173,7 +172,8 @@ in
             target = mkDefault name;
             source = mkIf (config.text != null) (
               let name' = "etc-" + baseNameOf name;
-              in mkDefault (pkgs.writeText name' config.text));
+              in mkDerivedConfig options.text (pkgs.writeText name')
+            );
           };
 
         }));
diff --git a/nixos/modules/system/etc/setup-etc.pl b/nixos/modules/system/etc/setup-etc.pl
index eed20065087fa..be6b2d9ae71ef 100644
--- a/nixos/modules/system/etc/setup-etc.pl
+++ b/nixos/modules/system/etc/setup-etc.pl
@@ -138,3 +138,9 @@ foreach my $fn (@oldCopied) {
 # Rewrite /etc/.clean.
 close CLEAN;
 write_file("/etc/.clean", map { "$_\n" } @copied);
+
+# Create /etc/NIXOS tag if not exists.
+# When /etc is not on a persistent filesystem, it will be wiped after reboot,
+# so we need to check and re-create it during activation.
+open TAG, ">>/etc/NIXOS";
+close TAG;
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index b19b688a1fb8a..b931b27ad8170 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -139,6 +139,7 @@ in {
         gzip
         gitMinimal
         config.nix.package.out
+        config.programs.ssh.package
       ];
 
       script = let
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 4f56504f45e74..225bcbe58e017 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -163,7 +163,7 @@ in
 
     fileSystems = mkOption {
       default = {};
-      example = literalExample ''
+      example = literalExpression ''
         {
           "/".device = "/dev/hda1";
           "/data" = {
diff --git a/nixos/modules/tasks/filesystems/ecryptfs.nix b/nixos/modules/tasks/filesystems/ecryptfs.nix
index 12a407cabbfb0..8138e65916109 100644
--- a/nixos/modules/tasks/filesystems/ecryptfs.nix
+++ b/nixos/modules/tasks/filesystems/ecryptfs.nix
@@ -7,8 +7,18 @@ with lib;
   config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
     system.fsPackages = [ pkgs.ecryptfs ];
     security.wrappers = {
-      "mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
-      "umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
+      "mount.ecryptfs_private" =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
+        };
+      "umount.ecryptfs_private" =
+        { setuid = true;
+          owner = "root";
+          group = "root";
+          source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
+        };
     };
   };
 }
diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix
index fd35c35d32adc..38c3920a78ada 100644
--- a/nixos/modules/tasks/filesystems/nfs.nix
+++ b/nixos/modules/tasks/filesystems/nfs.nix
@@ -35,7 +35,7 @@ in
           <link xlink:href="https://linux.die.net/man/5/idmapd.conf"/>
           for details.
         '';
-        example = literalExample ''
+        example = literalExpression ''
           {
             Translation = {
               GSS-Methods = "static,nsswitch";
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index cb0e66402476d..65364801c32aa 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -104,7 +104,7 @@ in
         readOnly = true;
         type = types.package;
         default = if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
-        defaultText = "if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs";
+        defaultText = literalExpression "if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs";
         description = "Configured ZFS userland tools package.";
       };
 
@@ -150,7 +150,6 @@ in
       devNodes = mkOption {
         type = types.path;
         default = "/dev/disk/by-id";
-        example = "/dev/disk/by-id";
         description = ''
           Name of directory from which to import ZFS devices.
 
@@ -351,7 +350,7 @@ in
 
       settings = mkOption {
         type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
-        example = literalExample ''
+        example = literalExpression ''
           {
             ZED_DEBUG_LOG = "/tmp/zed.debug.log";
 
@@ -562,7 +561,8 @@ in
                                   then cfgZfs.requestEncryptionCredentials
                                   else cfgZfs.requestEncryptionCredentials != []) ''
                   ${cfgZfs.package}/sbin/zfs list -rHo name,keylocation ${pool} | while IFS=$'\t' read ds kl; do
-                    (${optionalString (!isBool cfgZfs.requestEncryptionCredentials) ''
+                    {
+                      ${optionalString (!isBool cfgZfs.requestEncryptionCredentials) ''
                          if ! echo '${concatStringsSep "\n" cfgZfs.requestEncryptionCredentials}' | grep -qFx "$ds"; then
                            continue
                          fi
@@ -576,7 +576,8 @@ in
                       * )
                         ${cfgZfs.package}/sbin/zfs load-key "$ds"
                         ;;
-                    esac) < /dev/null # To protect while read ds kl in case anything reads stdin
+                    esac
+                    } < /dev/null # To protect while read ds kl in case anything reads stdin
                   done
                 ''}
                 echo "Successfully imported ${pool}"
diff --git a/nixos/modules/tasks/lvm.nix b/nixos/modules/tasks/lvm.nix
index aaa76b49fa303..35316603c38f2 100644
--- a/nixos/modules/tasks/lvm.nix
+++ b/nixos/modules/tasks/lvm.nix
@@ -9,7 +9,7 @@ in {
       type = types.package;
       default = if cfg.dmeventd.enable then pkgs.lvm2_dmeventd else pkgs.lvm2;
       internal = true;
-      defaultText = "pkgs.lvm2";
+      defaultText = literalExpression "pkgs.lvm2";
       description = ''
         This option allows you to override the LVM package that's used on the system
         (udev rules, tmpfiles, systemd services).
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index 11bd159319a3e..e8e2de090b32d 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -61,6 +61,8 @@ let
           MACAddress = i.macAddress;
         } // optionalAttrs (i.mtu != null) {
           MTUBytes = toString i.mtu;
+        } // optionalAttrs (i.wakeOnLan.enable == true) {
+          WakeOnLan = "magic";
         };
       };
     in listToAttrs (map createNetworkLink interfaces);
@@ -464,6 +466,39 @@ let
             '';
           });
 
+        createFouEncapsulation = n: v: nameValuePair "${n}-fou-encap"
+          (let
+            # if we have a device to bind to we can wait for its addresses to be
+            # configured, otherwise external sequencing is required.
+            deps = optionals (v.local != null && v.local.dev != null)
+              (deviceDependency v.local.dev ++ [ "network-addresses-${v.local.dev}.service" ]);
+            fouSpec = "port ${toString v.port} ${
+              if v.protocol != null then "ipproto ${toString v.protocol}" else "gue"
+            } ${
+              optionalString (v.local != null) "local ${escapeShellArg v.local.address} ${
+                optionalString (v.local.dev != null) "dev ${escapeShellArg v.local.dev}"
+              }"
+            }";
+          in
+          { description = "FOU endpoint ${n}";
+            wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+            bindsTo = deps;
+            partOf = [ "network-setup.service" ];
+            after = [ "network-pre.target" ] ++ deps;
+            before = [ "network-setup.service" ];
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute2 ];
+            script = ''
+              # always remove previous incarnation since show can't filter
+              ip fou del ${fouSpec} >/dev/null 2>&1 || true
+              ip fou add ${fouSpec}
+            '';
+            postStop = ''
+              ip fou del ${fouSpec} || true
+            '';
+          });
+
         createSitDevice = n: v: nameValuePair "${n}-netdev"
           (let
             deps = deviceDependency v.dev;
@@ -484,7 +519,12 @@ let
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
                 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
-                ${optionalString (v.dev != null) "dev \"${v.dev}\""}
+                ${optionalString (v.dev != null) "dev \"${v.dev}\""} \
+                ${optionalString (v.encapsulation != null)
+                  "encap ${v.encapsulation.type} encap-dport ${toString v.encapsulation.port} ${
+                    optionalString (v.encapsulation.sourcePort != null)
+                      "encap-sport ${toString v.encapsulation.sourcePort}"
+                  }"}
               ip link set "${n}" up
             '';
             postStop = ''
@@ -528,6 +568,7 @@ let
          // mapAttrs' createVswitchDevice cfg.vswitches
          // mapAttrs' createBondDevice cfg.bonds
          // mapAttrs' createMacvlanDevice cfg.macvlans
+         // mapAttrs' createFouEncapsulation cfg.fooOverUDP
          // mapAttrs' createSitDevice cfg.sits
          // mapAttrs' createVlanDevice cfg.vlans
          // {
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 225f9dc67fcc9..ccfd7fd4132b9 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -47,6 +47,9 @@ in
     } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
       assertion = !rstp;
       message = "networking.bridges.${n}.rstp is not supported by networkd.";
+    }) ++ flip mapAttrsToList cfg.fooOverUDP (n: { local, ... }: {
+      assertion = local == null;
+      message = "networking.fooOverUDP.${n}.local is not supported by networkd.";
     });
 
     networking.dhcpcd.enable = mkDefault false;
@@ -194,6 +197,23 @@ in
           macvlan = [ name ];
         } ]);
       })))
+      (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = "fou";
+          };
+          # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes,
+          # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature
+          # in networkd.
+          fooOverUDPConfig = {
+            Port = fou.port;
+            Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation";
+          } // (optionalAttrs (fou.protocol != null) {
+            Protocol = fou.protocol;
+          });
+        };
+      })))
       (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
         netdevs."40-${name}" = {
           netdevConfig = {
@@ -207,7 +227,17 @@ in
               Local = sit.local;
             }) // (optionalAttrs (sit.ttl != null) {
               TTL = sit.ttl;
-            });
+            }) // (optionalAttrs (sit.encapsulation != null) (
+              {
+                FooOverUDP = true;
+                Encapsulation =
+                  if sit.encapsulation.type == "fou"
+                  then "FooOverUDP"
+                  else "GenericUDPEncapsulation";
+                FOUDestinationPort = sit.encapsulation.port;
+              } // (optionalAttrs (sit.encapsulation.sourcePort != null) {
+                FOUSourcePort = sit.encapsulation.sourcePort;
+              })));
         };
         networks = mkIf (sit.dev != null) {
           "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 8f9c66b01572c..49901cda848df 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -10,6 +10,8 @@ let
   hasVirtuals = any (i: i.virtual) interfaces;
   hasSits = cfg.sits != { };
   hasBonds = cfg.bonds != { };
+  hasFous = cfg.fooOverUDP != { }
+    || filterAttrs (_: s: s.encapsulation != null) cfg.sits != { };
 
   slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds)
     ++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
@@ -146,7 +148,7 @@ let
       tempAddress = mkOption {
         type = types.enum (lib.attrNames tempaddrValues);
         default = cfg.tempAddresses;
-        defaultText = literalExample ''config.networking.tempAddresses'';
+        defaultText = literalExpression ''config.networking.tempAddresses'';
         description = ''
           When IPv6 is enabled with SLAAC, this option controls the use of
           temporary address (aka privacy extensions) on this
@@ -257,7 +259,7 @@ let
 
       virtualType = mkOption {
         default = if hasPrefix "tun" name then "tun" else "tap";
-        defaultText = literalExample ''if hasPrefix "tun" name then "tun" else "tap"'';
+        defaultText = literalExpression ''if hasPrefix "tun" name then "tun" else "tap"'';
         type = with types; enum [ "tun" "tap" ];
         description = ''
           The type of interface to create.
@@ -284,6 +286,13 @@ let
         '';
       };
 
+      wakeOnLan = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Wether to enable wol on this interface.";
+        };
+      };
     };
 
     config = {
@@ -408,7 +417,11 @@ in
         network node hostname (uname --nodename) the option
         boot.kernel.sysctl."kernel.hostname" can be used as a workaround (but
         the 64 character limit still applies).
+
+        WARNING: Do not use underscores (_) or you may run into unexpected issues.
       '';
+       # warning until the issues in https://github.com/NixOS/nixpkgs/pull/138978
+       # are resolved
     };
 
     networking.fqdn = mkOption {
@@ -420,7 +433,7 @@ in
           The FQDN is required but cannot be determined. Please make sure that
           both networking.hostName and networking.domain are set properly.
         '';
-      defaultText = literalExample ''''${networking.hostName}.''${networking.domain}'';
+      defaultText = literalExpression ''"''${networking.hostName}.''${networking.domain}"'';
       description = ''
         The fully qualified domain name (FQDN) of this host. It is the result
         of combining networking.hostName and networking.domain. Using this
@@ -578,7 +591,6 @@ in
         options = {
 
           interfaces = mkOption {
-            example = [ "eth0" "eth1" ];
             description = "The physical network interfaces connected by the vSwitch.";
             type = with types; attrsOf (submodule vswitchInterfaceOpts);
           };
@@ -691,7 +703,7 @@ in
         '';
       in mkOption {
         default = { };
-        example = literalExample ''
+        example = literalExpression ''
           {
             bond0 = {
               interfaces = [ "eth0" "wlan0" ];
@@ -720,7 +732,7 @@ in
             driverOptions = mkOption {
               type = types.attrsOf types.str;
               default = {};
-              example = literalExample driverOptionsExample;
+              example = literalExpression driverOptionsExample;
               description = ''
                 Options for the bonding driver.
                 Documentation can be found in
@@ -784,7 +796,7 @@ in
 
     networking.macvlans = mkOption {
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           wan = {
             interface = "enp2s0";
@@ -817,9 +829,74 @@ in
       });
     };
 
+    networking.fooOverUDP = mkOption {
+      default = { };
+      example =
+        {
+          primary = { port = 9001; local = { address = "192.0.2.1"; dev = "eth0"; }; };
+          backup =  { port = 9002; };
+        };
+      description = ''
+        This option allows you to configure Foo Over UDP and Generic UDP Encapsulation
+        endpoints. See <citerefentry><refentrytitle>ip-fou</refentrytitle>
+        <manvolnum>8</manvolnum></citerefentry> for details.
+      '';
+      type = with types; attrsOf (submodule {
+        options = {
+          port = mkOption {
+            type = port;
+            description = ''
+              Local port of the encapsulation UDP socket.
+            '';
+          };
+
+          protocol = mkOption {
+            type = nullOr (ints.between 1 255);
+            default = null;
+            description = ''
+              Protocol number of the encapsulated packets. Specifying <literal>null</literal>
+              (the default) creates a GUE endpoint, specifying a protocol number will create
+              a FOU endpoint.
+            '';
+          };
+
+          local = mkOption {
+            type = nullOr (submodule {
+              options = {
+                address = mkOption {
+                  type = types.str;
+                  description = ''
+                    Local address to bind to. The address must be available when the FOU
+                    endpoint is created, using the scripted network setup this can be achieved
+                    either by setting <literal>dev</literal> or adding dependency information to
+                    <literal>systemd.services.&lt;name&gt;-fou-encap</literal>; it isn't supported
+                    when using networkd.
+                  '';
+                };
+
+                dev = mkOption {
+                  type = nullOr str;
+                  default = null;
+                  example = "eth0";
+                  description = ''
+                    Network device to bind to.
+                  '';
+                };
+              };
+            });
+            default = null;
+            example = { address = "203.0.113.22"; };
+            description = ''
+              Local address (and optionally device) to bind to using the given port.
+            '';
+          };
+        };
+      });
+    };
+
     networking.sits = mkOption {
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           hurricane = {
             remote = "10.0.0.1";
@@ -876,6 +953,44 @@ in
             '';
           };
 
+          encapsulation = with types; mkOption {
+            type = nullOr (submodule {
+              options = {
+                type = mkOption {
+                  type = enum [ "fou" "gue" ];
+                  description = ''
+                    Selects encapsulation type. See
+                    <citerefentry><refentrytitle>ip-link</refentrytitle>
+                    <manvolnum>8</manvolnum></citerefentry> for details.
+                  '';
+                };
+
+                port = mkOption {
+                  type = port;
+                  example = 9001;
+                  description = ''
+                    Destination port for encapsulated packets.
+                  '';
+                };
+
+                sourcePort = mkOption {
+                  type = nullOr types.port;
+                  default = null;
+                  example = 9002;
+                  description = ''
+                    Source port for encapsulated packets. Will be chosen automatically by
+                    the kernel if unset.
+                  '';
+                };
+              };
+            });
+            default = null;
+            example = { type = "fou"; port = 9001; };
+            description = ''
+              Configures encapsulation in UDP packets.
+            '';
+          };
+
         };
 
       });
@@ -883,7 +998,7 @@ in
 
     networking.vlans = mkOption {
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           vlan0 = {
             id = 3;
@@ -927,7 +1042,7 @@ in
 
     networking.wlanInterfaces = mkOption {
       default = { };
-      example = literalExample ''
+      example = literalExpression ''
         {
           wlan-station0 = {
               device = "wlp6s0";
@@ -1110,7 +1225,8 @@ in
     boot.kernelModules = [ ]
       ++ optional hasVirtuals "tun"
       ++ optional hasSits "sit"
-      ++ optional hasBonds "bonding";
+      ++ optional hasBonds "bonding"
+      ++ optional hasFous "fou";
 
     boot.extraModprobeConfig =
       # This setting is intentional as it prevents default bond devices
@@ -1121,6 +1237,8 @@ in
       "net.ipv4.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
       "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
       "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
+      # networkmanager falls back to "/proc/sys/net/ipv6/conf/default/use_tempaddr"
+      "net.ipv6.conf.default.use_tempaddr" = tempaddrValues.${cfg.tempAddresses}.sysctl;
     } // listToAttrs (flip concatMap (filter (i: i.proxyARP) interfaces)
         (i: [(nameValuePair "net.ipv4.conf.${replaceChars ["."] ["/"] i.name}.proxy_arp" true)]))
       // listToAttrs (forEach interfaces
@@ -1133,11 +1251,18 @@ in
     # kernel because we need the ambient capability
     security.wrappers = if (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") then {
       ping = {
-        source  = "${pkgs.iputils.out}/bin/ping";
+        owner = "root";
+        group = "root";
         capabilities = "cap_net_raw+p";
+        source = "${pkgs.iputils.out}/bin/ping";
       };
     } else {
-      ping.source = "${pkgs.iputils.out}/bin/ping";
+      ping = {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.iputils.out}/bin/ping";
+      };
     };
     security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
       /run/wrappers/bin/ping {
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index be5fa88b8ade1..a7011be7e0429 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -4,7 +4,10 @@
 { options, config, lib, pkgs, ... }:
 
 with lib;
-with import ../../lib/qemu-flags.nix { inherit pkgs; };
+
+let
+  qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
+in
 
 {
 
@@ -12,8 +15,8 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
     systemd.services.backdoor =
       { wantedBy = [ "multi-user.target" ];
-        requires = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ];
-        after = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ];
+        requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+        after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
         script =
           ''
             export USER=root
@@ -30,7 +33,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
             cd /tmp
             exec < /dev/hvc0 > /dev/hvc0
-            while ! exec 2> /dev/${qemuSerialDevice}; do sleep 0.1; done
+            while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
             echo "connecting to host..." >&2
             stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
             echo
@@ -42,7 +45,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
     # Prevent agetty from being instantiated on the serial device, since it
     # interferes with the backdoor (writes to it will randomly fail
     # with EIO).  Likewise for hvc0.
-    systemd.services."serial-getty@${qemuSerialDevice}".enable = false;
+    systemd.services."serial-getty@${qemu-common.qemuSerialDevice}".enable = false;
     systemd.services."serial-getty@hvc0".enable = false;
 
     # Only set these settings when the options exist. Some tests (e.g. those
@@ -57,7 +60,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
         #       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
-        consoles = [ qemuSerialDevice ];
+        consoles = [ qemu-common.qemuSerialDevice ];
         package  = lib.mkDefault pkgs.qemu_test;
       };
     };
@@ -88,7 +91,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
     # Panic if an error occurs in stage 1 (rather than waiting for
     # user intervention).
     boot.kernelParams =
-      [ "console=${qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ];
+      [ "console=${qemu-common.qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ];
 
     # `xwininfo' is used by the test driver to query open windows.
     environment.systemPackages = [ pkgs.xorg.xwininfo ];
diff --git a/nixos/modules/virtualisation/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index 7b096bd1a9fbb..a4da62eb5f790 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -35,7 +35,7 @@ in
 
     image = mkOption {
       default = pkgs.anbox.image;
-      example = literalExample "pkgs.anbox.image";
+      defaultText = literalExpression "pkgs.anbox.image";
       type = types.package;
       description = ''
         Base android image for Anbox.
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index 41f3fa0e6642e..bd8c7f8c1eea3 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -76,7 +76,7 @@ in
 
   config = mkIf cfg.enable {
     assertions = [ {
-      assertion = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+      assertion = pkgs.stdenv.hostPlatform.isx86;
       message = "Azure not currently supported on ${pkgs.stdenv.hostPlatform.system}";
     } {
       assertion = config.networking.networkmanager.enable == false;
diff --git a/nixos/modules/virtualisation/containerd.nix b/nixos/modules/virtualisation/containerd.nix
index 43cb6273f253a..898a66e7b04e3 100644
--- a/nixos/modules/virtualisation/containerd.nix
+++ b/nixos/modules/virtualisation/containerd.nix
@@ -53,8 +53,11 @@ in
     virtualisation.containerd = {
       args.config = toString containerdConfigChecked;
       settings = {
-        plugins.cri.containerd.snapshotter = lib.mkIf config.boot.zfs.enabled "zfs";
-        plugins.cri.cni.bin_dir = lib.mkDefault "${pkgs.cni-plugins}/bin";
+        plugins."io.containerd.grpc.v1.cri" = {
+         containerd.snapshotter =
+           lib.mkIf config.boot.zfs.enabled (lib.mkOptionDefault "zfs");
+         cni.bin_dir = lib.mkOptionDefault "${pkgs.cni-plugins}/bin";
+        };
       };
     };
 
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index 84824e2f90f0a..cea3d51d3aefe 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -2,7 +2,7 @@
 let
   cfg = config.virtualisation.containers;
 
-  inherit (lib) mkOption types;
+  inherit (lib) literalExpression mkOption types;
 
   toml = pkgs.formats.toml { };
 in
@@ -50,12 +50,12 @@ in
 
     containersConf.cniPlugins = mkOption {
       type = types.listOf types.package;
-      defaultText = ''
+      defaultText = literalExpression ''
         [
           pkgs.cni-plugins
         ]
       '';
-      example = lib.literalExample ''
+      example = literalExpression ''
         [
           pkgs.cniPlugins.dnsname
         ]
@@ -106,7 +106,7 @@ in
     policy = mkOption {
       default = {};
       type = types.attrs;
-      example = lib.literalExample ''
+      example = literalExpression ''
         {
           default = [ { type = "insecureAcceptAnything"; } ];
           transports = {
diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix
index c135081959a6e..38766113f3916 100644
--- a/nixos/modules/virtualisation/cri-o.nix
+++ b/nixos/modules/virtualisation/cri-o.nix
@@ -38,27 +38,27 @@ in
       type = types.nullOr types.str;
       default = null;
       description = "Override the default pause image for pod sandboxes";
-      example = [ "k8s.gcr.io/pause:3.2" ];
+      example = "k8s.gcr.io/pause:3.2";
     };
 
     pauseCommand = mkOption {
       type = types.nullOr types.str;
       default = null;
       description = "Override the default pause command";
-      example = [ "/pause" ];
+      example = "/pause";
     };
 
     runtime = mkOption {
       type = types.nullOr types.str;
       default = null;
       description = "Override the default runtime";
-      example = [ "crun" ];
+      example = "crun";
     };
 
     extraPackages = mkOption {
       type = with types; listOf package;
       default = [ ];
-      example = literalExample ''
+      example = literalExpression ''
         [
           pkgs.gvisor
         ]
diff --git a/nixos/modules/virtualisation/digital-ocean-init.nix b/nixos/modules/virtualisation/digital-ocean-init.nix
index 02f4de009fa8c..4339d91de168e 100644
--- a/nixos/modules/virtualisation/digital-ocean-init.nix
+++ b/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -20,7 +20,7 @@ in {
   options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
     type = types.path;
     default = defaultConfigFile;
-    defaultText = ''
+    defaultText = literalDocBook ''
       The default configuration imports user-data if applicable and
       <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
     '';
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index 29f133786d8dd..06858e150309c 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -138,8 +138,9 @@ in
 
     package = mkOption {
       default = pkgs.docker;
+      defaultText = literalExpression "pkgs.docker";
       type = types.package;
-      example = pkgs.docker-edge;
+      example = literalExpression "pkgs.docker-edge";
       description = ''
         Docker package to be used in the module.
       '';
diff --git a/nixos/modules/virtualisation/ecs-agent.nix b/nixos/modules/virtualisation/ecs-agent.nix
index 93fefe56d1a5f..aa38a02ea0889 100644
--- a/nixos/modules/virtualisation/ecs-agent.nix
+++ b/nixos/modules/virtualisation/ecs-agent.nix
@@ -12,7 +12,7 @@ in {
       type = types.path;
       description = "The ECS agent package to use";
       default = pkgs.ecs-agent;
-      defaultText = "pkgs.ecs-agent";
+      defaultText = literalExpression "pkgs.ecs-agent";
     };
 
     extra-environment = mkOption {
diff --git a/nixos/modules/virtualisation/hyperv-guest.nix b/nixos/modules/virtualisation/hyperv-guest.nix
index b3bcfff19807f..fb6502644b80d 100644
--- a/nixos/modules/virtualisation/hyperv-guest.nix
+++ b/nixos/modules/virtualisation/hyperv-guest.nix
@@ -34,7 +34,7 @@ in {
       initrd.availableKernelModules = [ "hyperv_keyboard" ];
 
       kernelParams = [
-        "video=hyperv_fb:${cfg.videoMode} elevator=noop"
+        "video=hyperv_fb:${cfg.videoMode}" "elevator=noop"
       ];
     };
 
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index f45f1802d91cd..ab87394a30eee 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -13,23 +13,140 @@ let
   '';
   ovmfFilePrefix = if pkgs.stdenv.isAarch64 then "AAVMF" else "OVMF";
   qemuConfigFile = pkgs.writeText "qemu.conf" ''
-    ${optionalString cfg.qemuOvmf ''
+    ${optionalString cfg.qemu.ovmf.enable ''
       nvram = [ "/run/libvirt/nix-ovmf/${ovmfFilePrefix}_CODE.fd:/run/libvirt/nix-ovmf/${ovmfFilePrefix}_VARS.fd" ]
     ''}
-    ${optionalString (!cfg.qemuRunAsRoot) ''
+    ${optionalString (!cfg.qemu.runAsRoot) ''
       user = "qemu-libvirtd"
       group = "qemu-libvirtd"
     ''}
-    ${cfg.qemuVerbatimConfig}
+    ${cfg.qemu.verbatimConfig}
   '';
   dirName = "libvirt";
   subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
 
-in {
+  ovmfModule = types.submodule {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Allows libvirtd to take advantage of OVMF when creating new
+          QEMU VMs with UEFI boot.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.OVMF;
+        defaultText = literalExpression "pkgs.OVMF";
+        example = literalExpression "pkgs.OVMFFull";
+        description = ''
+          OVMF package to use.
+        '';
+      };
+    };
+  };
+
+  swtpmModule = types.submodule {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allows libvirtd to use swtpm to create an emulated TPM.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.swtpm;
+        defaultText = literalExpression "pkgs.swtpm";
+        description = ''
+          swtpm package to use.
+        '';
+      };
+    };
+  };
+
+  qemuModule = types.submodule {
+    options = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.qemu;
+        defaultText = literalExpression "pkgs.qemu";
+        description = ''
+          Qemu package to use with libvirt.
+          `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
+          `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
+        '';
+      };
+
+      runAsRoot = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If true,  libvirtd runs qemu as root.
+          If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
+          Changing this option to false may cause file permission issues
+          for existing guests. To fix these, manually change ownership
+          of affected files in /var/lib/libvirt/qemu to qemu-libvirtd.
+        '';
+      };
+
+      verbatimConfig = mkOption {
+        type = types.lines;
+        default = ''
+          namespaces = []
+        '';
+        description = ''
+          Contents written to the qemu configuration file, qemu.conf.
+          Make sure to include a proper namespace configuration when
+          supplying custom configuration.
+        '';
+      };
+
+      ovmf = mkOption {
+        type = ovmfModule;
+        default = { };
+        description = ''
+          QEMU's OVMF options.
+        '';
+      };
+
+      swtpm = mkOption {
+        type = swtpmModule;
+        default = { };
+        description = ''
+          QEMU's swtpm options.
+        '';
+      };
+    };
+  };
+in
+{
 
   imports = [
     (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
-      "Set the option `virtualisation.libvirtd.qemuPackage' instead.")
+      "Set the option `virtualisation.libvirtd.qemu.package' instead.")
+    (mkRenamedOptionModule
+      [ "virtualisation" "libvirtd" "qemuPackage" ]
+      [ "virtualisation" "libvirtd" "qemu" "package" ])
+    (mkRenamedOptionModule
+      [ "virtualisation" "libvirtd" "qemuRunAsRoot" ]
+      [ "virtualisation" "libvirtd" "qemu" "runAsRoot" ])
+    (mkRenamedOptionModule
+      [ "virtualisation" "libvirtd" "qemuVerbatimConfig" ]
+      [ "virtualisation" "libvirtd" "qemu" "verbatimConfig" ])
+    (mkRenamedOptionModule
+      [ "virtualisation" "libvirtd" "qemuOvmf" ]
+      [ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ])
+    (mkRenamedOptionModule
+      [ "virtualisation" "libvirtd" "qemuOvmfPackage" ]
+      [ "virtualisation" "libvirtd" "qemu" "ovmf" "package" ])
+    (mkRenamedOptionModule
+      [ "virtualisation" "libvirtd" "qemuSwtpm" ]
+      [ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ])
   ];
 
   ###### interface
@@ -50,22 +167,12 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.libvirt;
-      defaultText = "pkgs.libvirt";
+      defaultText = literalExpression "pkgs.libvirt";
       description = ''
         libvirt package to use.
       '';
     };
 
-    qemuPackage = mkOption {
-      type = types.package;
-      default = pkgs.qemu;
-      description = ''
-        Qemu package to use with libvirt.
-        `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
-        `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
-      '';
-    };
-
     extraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -75,39 +182,6 @@ in {
       '';
     };
 
-    qemuRunAsRoot = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        If true,  libvirtd runs qemu as root.
-        If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
-        Changing this option to false may cause file permission issues
-        for existing guests. To fix these, manually change ownership
-        of affected files in /var/lib/libvirt/qemu to qemu-libvirtd.
-      '';
-    };
-
-    qemuVerbatimConfig = mkOption {
-      type = types.lines;
-      default = ''
-        namespaces = []
-      '';
-      description = ''
-        Contents written to the qemu configuration file, qemu.conf.
-        Make sure to include a proper namespace configuration when
-        supplying custom configuration.
-      '';
-    };
-
-    qemuOvmf = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Allows libvirtd to take advantage of OVMF when creating new
-        QEMU VMs with UEFI boot.
-      '';
-    };
-
     extraOptions = mkOption {
       type = types.listOf types.str;
       default = [ ];
@@ -118,7 +192,7 @@ in {
     };
 
     onBoot = mkOption {
-      type = types.enum ["start" "ignore" ];
+      type = types.enum [ "start" "ignore" ];
       default = "start";
       description = ''
         Specifies the action to be done to / on the guests when the host boots.
@@ -130,7 +204,7 @@ in {
     };
 
     onShutdown = mkOption {
-      type = types.enum ["shutdown" "suspend" ];
+      type = types.enum [ "shutdown" "suspend" ];
       default = "suspend";
       description = ''
         When shutting down / restarting the host what method should
@@ -148,6 +222,13 @@ in {
       '';
     };
 
+    qemu = mkOption {
+      type = qemuModule;
+      default = { };
+      description = ''
+        QEMU related options.
+      '';
+    };
   };
 
 
@@ -160,14 +241,20 @@ in {
         assertion = config.security.polkit.enable;
         message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
       }
+      {
+        assertion = builtins.elem "fd" cfg.qemu.ovmf.package.outputs;
+        message = "The option 'virtualisation.libvirtd.qemuOvmfPackage' needs a package that has an 'fd' output.";
+      }
     ];
 
     environment = {
       # this file is expected in /etc/qemu and not sysconfdir (/var/lib)
-      etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n" (e:
-        "allow ${e}") cfg.allowedBridges;
-      systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemuPackage ];
-      etc.ethertypes.source = "${pkgs.ebtables}/etc/ethertypes";
+      etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n"
+        (e:
+          "allow ${e}")
+        cfg.allowedBridges;
+      systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ];
+      etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
     };
 
     boot.kernelModules = [ "tun" ];
@@ -183,6 +270,9 @@ in {
     };
 
     security.wrappers.qemu-bridge-helper = {
+      setuid = true;
+      owner = "root";
+      group = "root";
       source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
     };
 
@@ -205,17 +295,17 @@ in {
         cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf
 
         # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs
-        for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemuPackage}/bin/qemu-kvm ${cfg.qemuPackage}/bin/qemu-system-*; do
+        for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemu.package}/bin/qemu-kvm ${cfg.qemu.package}/bin/qemu-system-*; do
           ln -s --force "$emulator" /run/${dirName}/nix-emulators/
         done
 
         for helper in libexec/qemu-bridge-helper bin/qemu-pr-helper; do
-          ln -s --force ${cfg.qemuPackage}/$helper /run/${dirName}/nix-helpers/
+          ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/
         done
 
-        ${optionalString cfg.qemuOvmf ''
-          ln -s --force ${pkgs.OVMF.fd}/FV/${ovmfFilePrefix}_CODE.fd /run/${dirName}/nix-ovmf/
-          ln -s --force ${pkgs.OVMF.fd}/FV/${ovmfFilePrefix}_VARS.fd /run/${dirName}/nix-ovmf/
+        ${optionalString cfg.qemu.ovmf.enable ''
+          ln -s --force ${cfg.qemu.ovmf.package.fd}/FV/${ovmfFilePrefix}_CODE.fd /run/${dirName}/nix-ovmf/
+          ln -s --force ${cfg.qemu.ovmf.package.fd}/FV/${ovmfFilePrefix}_VARS.fd /run/${dirName}/nix-ovmf/
         ''}
       '';
 
@@ -231,15 +321,20 @@ in {
     systemd.services.libvirtd = {
       requires = [ "libvirtd-config.service" ];
       after = [ "libvirtd-config.service" ]
-              ++ optional vswitch.enable "ovs-vswitchd.service";
+        ++ optional vswitch.enable "ovs-vswitchd.service";
 
       environment.LIBVIRTD_ARGS = escapeShellArgs (
-        [ "--config" configFile
-          "--timeout" "120"     # from ${libvirt}/var/lib/sysconfig/libvirtd
-        ] ++ cfg.extraOptions);
-
-      path = [ cfg.qemuPackage ] # libvirtd requires qemu-img to manage disk images
-             ++ optional vswitch.enable vswitch.package;
+        [
+          "--config"
+          configFile
+          "--timeout"
+          "120" # from ${libvirt}/var/lib/sysconfig/libvirtd
+        ] ++ cfg.extraOptions
+      );
+
+      path = [ cfg.qemu.package ] # libvirtd requires qemu-img to manage disk images
+        ++ optional vswitch.enable vswitch.package
+        ++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package;
 
       serviceConfig = {
         Type = "notify";
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index e47bd59dc016b..9816cc2332fbd 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -1,26 +1,174 @@
-{ lib, ... }:
+{ lib, config, pkgs, ... }:
 
 with lib;
 
+let
+  templateSubmodule = { ... }: {
+    options = {
+      enable = mkEnableOption "this template";
+
+      target = mkOption {
+        description = "Path in the container";
+        type = types.path;
+      };
+      template = mkOption {
+        description = ".tpl file for rendering the target";
+        type = types.path;
+      };
+      when = mkOption {
+        description = "Events which trigger a rewrite (create, copy)";
+        type = types.listOf (types.str);
+      };
+      properties = mkOption {
+        description = "Additional properties";
+        type = types.attrs;
+        default = {};
+      };
+    };
+  };
+
+  toYAML = name: data: pkgs.writeText name (generators.toYAML {} data);
+
+  cfg = config.virtualisation.lxc;
+  templates = if cfg.templates != {} then let
+    list = mapAttrsToList (name: value: { inherit name; } // value)
+      (filterAttrs (name: value: value.enable) cfg.templates);
+  in
+    {
+      files = map (tpl: {
+        source = tpl.template;
+        target = "/templates/${tpl.name}.tpl";
+      }) list;
+      properties = listToAttrs (map (tpl: nameValuePair tpl.target {
+        when = tpl.when;
+        template = "${tpl.name}.tpl";
+        properties = tpl.properties;
+      }) list);
+    }
+  else { files = []; properties = {}; };
+
+in
 {
   imports = [
-    ../profiles/docker-container.nix # FIXME, shouldn't include something from profiles/
+    ../installer/cd-dvd/channel.nix
+    ../profiles/minimal.nix
+    ../profiles/clone-config.nix
   ];
 
-  # Allow the user to login as root without password.
-  users.users.root.initialHashedPassword = mkOverride 150 "";
+  options = {
+    virtualisation.lxc = {
+      templates = mkOption {
+        description = "Templates for LXD";
+        type = types.attrsOf (types.submodule (templateSubmodule));
+        default = {};
+        example = literalExpression ''
+          {
+            # create /etc/hostname on container creation
+            "hostname" = {
+              enable = true;
+              target = "/etc/hostname";
+              template = builtins.writeFile "hostname.tpl" "{{ container.name }}";
+              when = [ "create" ];
+            };
+            # create /etc/nixos/hostname.nix with a configuration for keeping the hostname applied
+            "hostname-nix" = {
+              enable = true;
+              target = "/etc/nixos/hostname.nix";
+              template = builtins.writeFile "hostname-nix.tpl" "{ ... }: { networking.hostName = "{{ container.name }}"; }";
+              # copy keeps the file updated when the container is changed
+              when = [ "create" "copy" ];
+            };
+            # copy allow the user to specify a custom configuration.nix
+            "configuration-nix" = {
+              enable = true;
+              target = "/etc/nixos/configuration.nix";
+              template = builtins.writeFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
+              when = [ "create" ];
+            };
+          };
+        '';
+      };
+    };
+  };
+
+  config = {
+    boot.isContainer = true;
+    boot.postBootCommands =
+      ''
+        # After booting, register the contents of the Nix store in the Nix
+        # database.
+        if [ -f /nix-path-registration ]; then
+          ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
+          rm /nix-path-registration
+        fi
+
+        # nixos-rebuild also requires a "system" profile
+        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+      '';
+
+    system.build.metadata = pkgs.callPackage ../../lib/make-system-tarball.nix {
+      contents = [
+        {
+          source = toYAML "metadata.yaml" {
+            architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.system)) 0;
+            creation_date = 1;
+            properties = {
+              description = "NixOS ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
+              os = "nixos";
+              release = "${config.system.nixos.codeName}";
+            };
+            templates = templates.properties;
+          };
+          target = "/metadata.yaml";
+        }
+      ] ++ templates.files;
+    };
 
-  # Some more help text.
-  services.getty.helpLine =
-    ''
+    # TODO: build rootfs as squashfs for faster unpack
+    system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
+      extraArgs = "--owner=0";
 
-      Log in as "root" with an empty password.
+      storeContents = [
+        {
+          object = config.system.build.toplevel;
+          symlink = "none";
+        }
+      ];
+
+      contents = [
+        {
+          source = config.system.build.toplevel + "/init";
+          target = "/sbin/init";
+        }
+      ];
+
+      extraCommands = "mkdir -p proc sys dev";
+    };
+
+    # Add the overrides from lxd distrobuilder
+    systemd.extraConfig = ''
+      [Service]
+      ProtectProc=default
+      ProtectControlGroups=no
+      ProtectKernelTunables=no
     '';
 
-  # Containers should be light-weight, so start sshd on demand.
-  services.openssh.enable = mkDefault true;
-  services.openssh.startWhenNeeded = mkDefault true;
+    # Allow the user to login as root without password.
+    users.users.root.initialHashedPassword = mkOverride 150 "";
+
+    system.activationScripts.installInitScript = mkForce ''
+      ln -fs $systemConfig/init /sbin/init
+    '';
+
+    # Some more help text.
+    services.getty.helpLine =
+      ''
+
+        Log in as "root" with an empty password.
+      '';
 
-  # Allow ssh connections
-  networking.firewall.allowedTCPPorts = [ 22 ];
+    # Containers should be light-weight, so start sshd on demand.
+    services.openssh.enable = mkDefault true;
+    services.openssh.startWhenNeeded = mkDefault true;
+  };
 }
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index cde29f7bf59ce..18451b147ff53 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -34,8 +34,8 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.lxd.override { nftablesSupport = config.networking.nftables.enable; };
-        defaultText = "pkgs.lxd";
+        default = pkgs.lxd;
+        defaultText = literalExpression "pkgs.lxd";
         description = ''
           The LXD package to use.
         '';
@@ -44,7 +44,7 @@ in {
       lxcPackage = mkOption {
         type = types.package;
         default = pkgs.lxc;
-        defaultText = "pkgs.lxc";
+        defaultText = literalExpression "pkgs.lxc";
         description = ''
           The LXC package to use with LXD (required for AppArmor profiles).
         '';
@@ -53,7 +53,7 @@ in {
       zfsSupport = mkOption {
         type = types.bool;
         default = config.boot.zfs.enabled;
-        defaultText = "config.boot.zfs.enabled";
+        defaultText = literalExpression "config.boot.zfs.enabled";
         description = ''
           Enables lxd to use zfs as a storage for containers.
 
@@ -158,7 +158,7 @@ in {
       };
     };
 
-    users.groups.lxd.gid = config.ids.gids.lxd;
+    users.groups.lxd = {};
 
     users.users.root = {
       subUidRanges = [ { startUid = 1000000; count = 65536; } ];
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index f3f318412df1b..279c965673539 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -530,7 +530,7 @@ in
             nixpkgs = mkOption {
               type = types.path;
               default = pkgs.path;
-              defaultText = "pkgs.path";
+              defaultText = literalExpression "pkgs.path";
               description = ''
                 A path to the nixpkgs that provide the modules, pkgs and lib for evaluating the container.
 
@@ -636,7 +636,7 @@ in
             bindMounts = mkOption {
               type = with types; attrsOf (submodule bindMountOpts);
               default = {};
-              example = literalExample ''
+              example = literalExpression ''
                 { "/home" = { hostPath = "/home/alice";
                               isReadOnly = false; };
                 }
@@ -707,7 +707,7 @@ in
         }));
 
       default = {};
-      example = literalExample
+      example = literalExpression
         ''
           { webserver =
               { path = "/nix/var/nix/profiles/webserver";
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index a4a92f22506cf..24573bba48000 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -28,7 +28,7 @@ let
             You still need to set the <literal>image</literal> attribute, as it
             will be used as the image name for docker to start a container.
           '';
-          example = literalExample "pkgs.dockerTools.buildDockerImage {...};";
+          example = literalExpression "pkgs.dockerTools.buildDockerImage {...};";
         };
 
         login = {
@@ -59,7 +59,7 @@ let
           type =  with types; listOf str;
           default = [];
           description = "Commandline arguments to pass to the image's entrypoint.";
-          example = literalExample ''
+          example = literalExpression ''
             ["--port=9000"]
           '';
         };
@@ -75,7 +75,7 @@ let
           type = with types; attrsOf str;
           default = {};
           description = "Environment variables to set for this container.";
-          example = literalExample ''
+          example = literalExpression ''
             {
               DATABASE_HOST = "db.example.com";
               DATABASE_PORT = "3306";
@@ -87,7 +87,7 @@ let
           type = with types; listOf path;
           default = [];
           description = "Environment files for this container.";
-          example = literalExample ''
+          example = literalExpression ''
             [
               /path/to/.env
               /path/to/.env.secret
@@ -160,7 +160,7 @@ let
             <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
             Docker engine documentation</link> for full details.
           '';
-          example = literalExample ''
+          example = literalExpression ''
             [
               "8080:9000"
             ]
@@ -191,7 +191,7 @@ let
             <link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
             docker engine documentation</link> for details.
           '';
-          example = literalExample ''
+          example = literalExpression ''
             [
               "volume_name:/path/inside/container"
               "/path/on/host:/path/inside/container"
@@ -214,7 +214,7 @@ let
 
             Use the same name as the attribute under <literal>virtualisation.oci-containers.containers</literal>.
           '';
-          example = literalExample ''
+          example = literalExpression ''
             virtualisation.oci-containers.containers = {
               node1 = {};
               node2 = {
@@ -228,7 +228,7 @@ let
           type = with types; listOf str;
           default = [];
           description = "Extra options for <command>${defaultBackend} run</command>.";
-          example = literalExample ''
+          example = literalExpression ''
             ["--network=host"]
           '';
         };
diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index ccf32641df626..325f6f5b43f4d 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -31,7 +31,7 @@ in {
     package = mkOption {
       type = types.package;
       default = pkgs.openvswitch;
-      defaultText = "pkgs.openvswitch";
+      defaultText = literalExpression "pkgs.openvswitch";
       description = ''
         Open vSwitch package to use.
       '';
diff --git a/nixos/modules/virtualisation/parallels-guest.nix b/nixos/modules/virtualisation/parallels-guest.nix
index 55605b388b7ca..d950cecff6f0c 100644
--- a/nixos/modules/virtualisation/parallels-guest.nix
+++ b/nixos/modules/virtualisation/parallels-guest.nix
@@ -34,8 +34,7 @@ in
       package = mkOption {
         type = types.nullOr types.package;
         default = config.boot.kernelPackages.prl-tools;
-        defaultText = "config.boot.kernelPackages.prl-tools";
-        example = literalExample "config.boot.kernelPackages.prl-tools";
+        defaultText = literalExpression "config.boot.kernelPackages.prl-tools";
         description = ''
           Defines which package to use for prl-tools. Override to change the version.
         '';
diff --git a/nixos/modules/virtualisation/podman.nix b/nixos/modules/virtualisation/podman.nix
index 893afee4c32de..385475c84a1aa 100644
--- a/nixos/modules/virtualisation/podman.nix
+++ b/nixos/modules/virtualisation/podman.nix
@@ -95,7 +95,7 @@ in
     extraPackages = mkOption {
       type = with types; listOf package;
       default = [ ];
-      example = lib.literalExample ''
+      example = lib.literalExpression ''
         [
           pkgs.gvisor
         ]
diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix
new file mode 100644
index 0000000000000..c537d5aed4471
--- /dev/null
+++ b/nixos/modules/virtualisation/proxmox-image.nix
@@ -0,0 +1,169 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options.proxmox = {
+    qemuConf = {
+      # essential configs
+      boot = mkOption {
+        type = types.str;
+        default = "";
+        example = "order=scsi0;net0";
+        description = ''
+          Default boot device. PVE will try all devices in its default order if this value is empty.
+        '';
+      };
+      scsihw = mkOption {
+        type = types.str;
+        default = "virtio-scsi-pci";
+        example = "lsi";
+        description = ''
+          SCSI controller type. Must be one of the supported values given in
+          <link xlink:href="https://pve.proxmox.com/wiki/Qemu/KVM_Virtual_Machines"/>
+        '';
+      };
+      virtio0 = mkOption {
+        type = types.str;
+        default = "local-lvm:vm-9999-disk-0";
+        example = "ceph:vm-123-disk-0";
+        description = ''
+          Configuration for the default virtio disk. It can be used as a cue for PVE to autodetect the target sotrage.
+          This parameter is required by PVE even if it isn't used.
+        '';
+      };
+      ostype = mkOption {
+        type = types.str;
+        default = "l26";
+        description = ''
+          Guest OS type
+        '';
+      };
+      cores = mkOption {
+        type = types.ints.positive;
+        default = 1;
+        description = ''
+          Guest core count
+        '';
+      };
+      memory = mkOption {
+        type = types.ints.positive;
+        default = 1024;
+        description = ''
+          Guest memory in MB
+        '';
+      };
+
+      # optional configs
+      name = mkOption {
+        type = types.str;
+        default = "nixos-${config.system.nixos.label}";
+        description = ''
+          VM name
+        '';
+      };
+      net0 = mkOption {
+        type = types.commas;
+        default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1";
+        description = ''
+          Configuration for the default interface. When restoring from VMA, check the
+          "unique" box to ensure device mac is randomized.
+        '';
+      };
+      serial0 = mkOption {
+        type = types.str;
+        default = "socket";
+        example = "/dev/ttyS0";
+        description = ''
+          Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device (i.e. /dev/ttyS0),
+          or create a unix socket on the host side (use qm terminal to open a terminal connection).
+        '';
+      };
+      agent = mkOption {
+        type = types.bool;
+        apply = x: if x then "1" else "0";
+        default = true;
+        description = ''
+          Expect guest to have qemu agent running
+        '';
+      };
+    };
+    qemuExtraConf = mkOption {
+      type = with types; attrsOf (oneOf [ str int ]);
+      default = {};
+      example = literalExpression ''{
+        cpu = "host";
+        onboot = 1;
+      }'';
+      description = ''
+        Additional options appended to qemu-server.conf
+      '';
+    };
+    filenameSuffix = mkOption {
+      type = types.str;
+      default = config.proxmox.qemuConf.name;
+      example = "999-nixos_template";
+      description = ''
+        Filename of the image will be vzdump-qemu-''${filenameSuffix}.vma.zstd.
+        This will also determine the default name of the VM on restoring the VMA.
+        Start this value with a number if you want the VMA to be detected as a backup of
+        any specific VMID.
+      '';
+    };
+  };
+
+  config = let
+    cfg = config.proxmox;
+    cfgLine = name: value: ''
+      ${name}: ${builtins.toString value}
+    '';
+    cfgFile = fileName: properties: pkgs.writeTextDir fileName ''
+      # generated by NixOS
+      ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)}
+      #qmdump#map:virtio0:drive-virtio0:local-lvm:raw:
+    '';
+  in {
+    system.build.VMA = import ../../lib/make-disk-image.nix {
+      name = "proxmox-${cfg.filenameSuffix}";
+      postVM = let
+        # Build qemu with PVE's patch that adds support for the VMA format
+        vma = pkgs.qemu_kvm.overrideAttrs ( super: {
+          patches = let
+            rev = "cc707c362ea5c8d832aac270d1ffa7ac66a8908f";
+            path = "debian/patches/pve/0025-PVE-Backup-add-vma-backup-format-code.patch";
+            vma-patch = pkgs.fetchpatch {
+              url = "https://git.proxmox.com/?p=pve-qemu.git;a=blob_plain;hb=${rev};f=${path}";
+              sha256 = "1z467xnmfmry3pjy7p34psd5xdil9x0apnbvfz8qbj0bf9fgc8zf";
+            };
+          in super.patches ++ [ vma-patch ];
+          buildInputs = super.buildInputs ++ [ pkgs.libuuid ];
+        });
+      in
+      ''
+        ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \
+          -c ${cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)}/qemu-server.conf drive-virtio0=$diskImage
+        rm $diskImage
+        ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma"
+        mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/
+      '';
+      format = "raw";
+      inherit config lib pkgs;
+    };
+
+    boot = {
+      growPartition = true;
+      kernelParams = [ "console=ttyS0" ];
+      loader.grub.device = lib.mkDefault "/dev/vda";
+      loader.timeout = 0;
+      initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ];
+    };
+
+    fileSystems."/" = {
+      device = "/dev/disk/by-label/nixos";
+      autoResize = true;
+      fsType = "ext4";
+    };
+
+    services.qemuGuest.enable = lib.mkDefault true;
+  };
+}
diff --git a/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixos/modules/virtualisation/qemu-guest-agent.nix
index 3824d0c168f78..39273e523e8f3 100644
--- a/nixos/modules/virtualisation/qemu-guest-agent.nix
+++ b/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -14,7 +14,8 @@ in {
       };
       package = mkOption {
         type = types.package;
-        default = pkgs.qemu.ga;
+        default = pkgs.qemu_kvm.ga;
+        defaultText = literalExpression "pkgs.qemu_kvm.ga";
         description = "The QEMU guest agent package.";
       };
   };
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index d9935bcafb716..493c407222f7c 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -10,10 +10,10 @@
 { config, lib, pkgs, options, ... }:
 
 with lib;
-with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
 let
 
+  qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
 
   cfg = config.virtualisation;
 
@@ -75,7 +75,7 @@ let
     in
       "-drive ${driveOpts} ${device}";
 
-  drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
+  drivesCmdLine = drives: concatStringsSep "\\\n    " (imap1 driveCmdline drives);
 
 
   # Creates a device name from a 1-based a numerical index, e.g.
@@ -97,7 +97,7 @@ let
     imap1 (idx: drive: drive // { device = driveDeviceName idx; });
 
   efiPrefix =
-    if (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then "${pkgs.OVMF.fd}/FV/OVMF"
+    if pkgs.stdenv.hostPlatform.isx86 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";
@@ -108,39 +108,50 @@ let
     ''
       #! ${pkgs.runtimeShell}
 
-      NIX_DISK_IMAGE=$(readlink -f ''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}})
+      set -e
+
+      NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}")
 
       if ! test -e "$NIX_DISK_IMAGE"; then
           ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
-            ${toString config.virtualisation.diskSize}M || exit 1
+            ${toString config.virtualisation.diskSize}M
       fi
 
       # Create a directory for storing temporary data of the running VM.
-      if [ -z "$TMPDIR" -o -z "$USE_TMPDIR" ]; then
+      if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then
           TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir)
       fi
 
+      ${lib.optionalString cfg.useNixStoreImage
+      ''
+        # Create a writable copy/snapshot of the store image.
+        ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${storeImage}/nixos.qcow2 "$TMPDIR"/store.img
+      ''}
+
       # Create a directory for exchanging data with the VM.
-      mkdir -p $TMPDIR/xchg
+      mkdir -p "$TMPDIR/xchg"
 
-      ${if cfg.useBootLoader then ''
+      ${lib.optionalString cfg.useBootLoader
+      ''
         # Create a writable copy/snapshot of the boot disk.
         # 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
+        ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img"
 
-        NIX_EFI_VARS=$(readlink -f ''${NIX_EFI_VARS:-${cfg.efiVars}})
+        NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}")
 
-        ${if cfg.useEFIBoot then ''
+        ${lib.optionalString cfg.useEFIBoot
+        ''
           # 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
+            cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS"
+            chmod 0644 "$NIX_EFI_VARS"
           fi
-        '' else ""}
-      '' else ""}
+        ''}
+      ''}
 
-      cd $TMPDIR
-      idx=0
+      cd "$TMPDIR"
+
+      ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
       ${flip concatMapStrings cfg.emptyDiskImages (size: ''
         if ! test -e "empty$idx.qcow2"; then
             ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
@@ -149,23 +160,24 @@ let
       '')}
 
       # Start QEMU.
-      exec ${qemuBinary qemu} \
+      exec ${qemu-common.qemuBinary qemu} \
           -name ${config.system.name} \
           -m ${toString config.virtualisation.memorySize} \
           -smp ${toString config.virtualisation.cores} \
           -device virtio-rng-pci \
           ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
-          -virtfs local,path=/nix/store,security_model=none,mount_tag=store \
-          -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
-          -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
+          ${concatStringsSep " \\\n    "
+            (mapAttrsToList
+              (tag: share: "-virtfs local,path=${share.source},security_model=none,mount_tag=${tag}")
+              config.virtualisation.sharedDirectories)} \
           ${drivesCmdLine config.virtualisation.qemu.drives} \
-          ${toString config.virtualisation.qemu.options} \
+          ${concatStringsSep " \\\n    " config.virtualisation.qemu.options} \
           $QEMU_OPTS \
           "$@"
     '';
 
 
-  regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.pathsInNixDB; };
+  regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; };
 
 
   # Generate a hard disk image containing a /boot partition and GRUB
@@ -257,11 +269,24 @@ let
         '' # */
     );
 
+  storeImage = import ../../lib/make-disk-image.nix {
+    inherit pkgs config lib;
+    additionalPaths = [ regInfo ];
+    format = "qcow2";
+    onlyNixStore = true;
+    partitionTableType = "none";
+    installBootLoader = false;
+    diskSize = "auto";
+    additionalSpace = "0M";
+    copyChannel = false;
+  };
+
 in
 
 {
   imports = [
     ../profiles/qemu-guest.nix
+    (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ])
   ];
 
   options = {
@@ -270,20 +295,21 @@ in
 
     virtualisation.memorySize =
       mkOption {
-        default = 384;
+        type = types.ints.positive;
+        default = 1024;
         description =
           ''
-            Memory size (M) of virtual machine.
+            The memory size in megabytes of the virtual machine.
           '';
       };
 
     virtualisation.msize =
       mkOption {
-        default = null;
-        type = types.nullOr types.ints.unsigned;
+        type = types.ints.positive;
+        default = pkgs.vmTools.default9PMsizeBytes;
         description =
           ''
-            msize (maximum packet size) option passed to 9p file systems, in
+            The msize (maximum packet size) option passed to 9p file systems, in
             bytes. Increasing this should increase performance significantly,
             at the cost of higher RAM usage.
           '';
@@ -291,15 +317,17 @@ in
 
     virtualisation.diskSize =
       mkOption {
-        default = 512;
+        type = types.nullOr types.ints.positive;
+        default = 1024;
         description =
           ''
-            Disk size (M) of virtual machine.
+            The disk size in megabytes of the virtual machine.
           '';
       };
 
     virtualisation.diskImage =
       mkOption {
+        type = types.str;
         default = "./${config.system.name}.qcow2";
         description =
           ''
@@ -311,7 +339,7 @@ in
 
     virtualisation.bootDevice =
       mkOption {
-        type = types.str;
+        type = types.path;
         example = "/dev/vda";
         description =
           ''
@@ -321,8 +349,8 @@ in
 
     virtualisation.emptyDiskImages =
       mkOption {
+        type = types.listOf types.ints.positive;
         default = [];
-        type = types.listOf types.int;
         description =
           ''
             Additional disk images to provide to the VM. The value is
@@ -333,6 +361,7 @@ in
 
     virtualisation.graphics =
       mkOption {
+        type = types.bool;
         default = true;
         description =
           ''
@@ -342,10 +371,20 @@ in
             '';
       };
 
+    virtualisation.resolution =
+      mkOption {
+        type = options.services.xserver.resolutions.type.nestedTypes.elemType;
+        default = { x = 1024; y = 768; };
+        description =
+          ''
+            The resolution of the virtual machine display.
+          '';
+      };
+
     virtualisation.cores =
       mkOption {
+        type = types.ints.positive;
         default = 1;
-        type = types.int;
         description =
           ''
             Specify the number of cores the guest is permitted to use.
@@ -354,21 +393,123 @@ in
           '';
       };
 
-    virtualisation.pathsInNixDB =
+    virtualisation.sharedDirectories =
       mkOption {
+        type = types.attrsOf
+          (types.submodule {
+            options.source = mkOption {
+              type = types.str;
+              description = "The path of the directory to share, can be a shell variable";
+            };
+            options.target = mkOption {
+              type = types.path;
+              description = "The mount point of the directory inside the virtual machine";
+            };
+          });
+        default = { };
+        example = {
+          my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
+        };
+        description =
+          ''
+            An attributes set of directories that will be shared with the
+            virtual machine using VirtFS (9P filesystem over VirtIO).
+            The attribute name will be used as the 9P mount tag.
+          '';
+      };
+
+    virtualisation.additionalPaths =
+      mkOption {
+        type = types.listOf types.path;
         default = [];
         description =
           ''
-            The list of paths whose closure is registered in the Nix
-            database in the VM.  All other paths in the host Nix store
+            A list of paths whose closure should be made available to
+            the VM.
+
+            When 9p is used, the closure is registered in the Nix
+            database in the VM. All other paths in the host Nix store
             appear in the guest Nix store as well, but are considered
             garbage (because they are not registered in the Nix
-            database in the guest).
+            database of the guest).
+
+            When <option>virtualisation.useNixStoreImage</option> is
+            set, the closure is copied to the Nix store image.
           '';
       };
 
+    virtualisation.forwardPorts = mkOption {
+      type = types.listOf
+        (types.submodule {
+          options.from = mkOption {
+            type = types.enum [ "host" "guest" ];
+            default = "host";
+            description =
+              ''
+                Controls the direction in which the ports are mapped:
+
+                - <literal>"host"</literal> means traffic from the host ports
+                is forwarded to the given guest port.
+
+                - <literal>"guest"</literal> means traffic from the guest ports
+                is forwarded to the given host port.
+              '';
+          };
+          options.proto = mkOption {
+            type = types.enum [ "tcp" "udp" ];
+            default = "tcp";
+            description = "The protocol to forward.";
+          };
+          options.host.address = mkOption {
+            type = types.str;
+            default = "";
+            description = "The IPv4 address of the host.";
+          };
+          options.host.port = mkOption {
+            type = types.port;
+            description = "The host port to be mapped.";
+          };
+          options.guest.address = mkOption {
+            type = types.str;
+            default = "";
+            description = "The IPv4 address on the guest VLAN.";
+          };
+          options.guest.port = mkOption {
+            type = types.port;
+            description = "The guest port to be mapped.";
+          };
+        });
+      default = [];
+      example = lib.literalExpression
+        ''
+        [ # forward local port 2222 -> 22, to ssh into the VM
+          { from = "host"; host.port = 2222; guest.port = 22; }
+
+          # forward local port 80 -> 10.0.2.10:80 in the VLAN
+          { from = "guest";
+            guest.address = "10.0.2.10"; guest.port = 80;
+            host.address = "127.0.0.1"; host.port = 80;
+          }
+        ]
+        '';
+      description =
+        ''
+          When using the SLiRP user networking (default), this option allows to
+          forward ports to/from the host/guest.
+
+          <warning><para>
+            If the NixOS firewall on the virtual machine is enabled, you also
+            have to open the guest ports to enable the traffic between host and
+            guest.
+          </para></warning>
+
+          <note><para>Currently QEMU supports only IPv4 forwarding.</para></note>
+        '';
+    };
+
     virtualisation.vlans =
       mkOption {
+        type = types.listOf types.ints.unsigned;
         default = [ 1 ];
         example = [ 1 2 ];
         description =
@@ -386,6 +527,7 @@ in
 
     virtualisation.writableStore =
       mkOption {
+        type = types.bool;
         default = true; # FIXME
         description =
           ''
@@ -397,6 +539,7 @@ in
 
     virtualisation.writableStoreUseTmpfs =
       mkOption {
+        type = types.bool;
         default = true;
         description =
           ''
@@ -407,6 +550,7 @@ in
 
     networking.primaryIPAddress =
       mkOption {
+        type = types.str;
         default = "";
         internal = true;
         description = "Primary IP address used in /etc/hosts.";
@@ -416,14 +560,14 @@ in
       package =
         mkOption {
           type = types.package;
-          default = pkgs.qemu;
+          default = pkgs.qemu_kvm;
           example = "pkgs.qemu_test";
           description = "QEMU package to use.";
         };
 
       options =
         mkOption {
-          type = types.listOf types.unspecified;
+          type = types.listOf types.str;
           default = [];
           example = [ "-vga std" ];
           description = "Options passed to QEMU.";
@@ -432,7 +576,7 @@ in
       consoles = mkOption {
         type = types.listOf types.str;
         default = let
-          consoles = [ "${qemuSerialDevice},115200n8" "tty0" ];
+          consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ];
         in if cfg.graphics then consoles else reverseList consoles;
         example = [ "console=tty1" ];
         description = ''
@@ -448,17 +592,18 @@ in
 
       networkingOptions =
         mkOption {
-          default = [
+          type = types.listOf types.str;
+          default = [ ];
+          example = [
             "-net nic,netdev=user.0,model=virtio"
-            "-netdev user,id=user.0\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
+            "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
           ];
-          type = types.listOf types.str;
           description = ''
             Networking-related command-line options that should be passed to qemu.
-            The default is to use userspace networking (slirp).
+            The default is to use userspace networking (SLiRP).
 
             If you override this option, be advised to keep
-            ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the default)
+            ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the example)
             to keep the default runtime behaviour.
           '';
         };
@@ -472,24 +617,39 @@ in
 
       diskInterface =
         mkOption {
+          type = types.enum [ "virtio" "scsi" "ide" ];
           default = "virtio";
           example = "scsi";
-          type = types.enum [ "virtio" "scsi" "ide" ];
           description = "The interface used for the virtual hard disks.";
         };
 
       guestAgent.enable =
         mkOption {
-          default = true;
           type = types.bool;
+          default = true;
           description = ''
             Enable the Qemu guest agent.
           '';
         };
     };
 
+    virtualisation.useNixStoreImage =
+      mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Build and use a disk image for the Nix store, instead of
+          accessing the host's one through 9p.
+
+          For applications which do a lot of reads from the store,
+          this can drastically improve performance, but at the cost of
+          disk space and image build time.
+        '';
+      };
+
     virtualisation.useBootLoader =
       mkOption {
+        type = types.bool;
         default = false;
         description =
           ''
@@ -504,6 +664,7 @@ in
 
     virtualisation.useEFIBoot =
       mkOption {
+        type = types.bool;
         default = false;
         description =
           ''
@@ -515,6 +676,7 @@ in
 
     virtualisation.efiVars =
       mkOption {
+        type = types.str;
         default = "./${config.system.name}-efi-vars.fd";
         description =
           ''
@@ -525,8 +687,8 @@ in
 
     virtualisation.bios =
       mkOption {
-        default = null;
         type = types.nullOr types.package;
+        default = null;
         description =
           ''
             An alternate BIOS (such as <package>qboot</package>) with which to start the VM.
@@ -539,6 +701,25 @@ in
 
   config = {
 
+    assertions =
+      lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule:
+        [
+          { assertion = rule.from == "guest" -> rule.proto == "tcp";
+            message =
+              ''
+                Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto:
+                  Guest forwarding supports only TCP connections.
+              '';
+          }
+          { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address;
+            message =
+              ''
+                Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address:
+                  The address must be in the default VLAN (10.0.2.0/24).
+              '';
+          }
+        ]));
+
     # Note [Disk layout with `useBootLoader`]
     #
     # If `useBootLoader = true`, we configure 2 drives:
@@ -560,6 +741,7 @@ in
         then driveDeviceName 2 # second disk
         else cfg.bootDevice
     );
+    boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
 
     boot.initrd.extraUtilsCommands =
       ''
@@ -597,7 +779,7 @@ in
       '';
 
     # After booting, register the closure of the paths in
-    # `virtualisation.pathsInNixDB' in the Nix database in the VM.  This
+    # `virtualisation.additionalPaths' in the Nix database in the VM.  This
     # allows Nix operations to work in the VM.  The path to the
     # registration file is passed through the kernel command line to
     # allow `system.build.toplevel' to be included.  (If we had a direct
@@ -616,11 +798,42 @@ in
 
     virtualisation.bootDevice = mkDefault (driveDeviceName 1);
 
-    virtualisation.pathsInNixDB = [ config.system.build.toplevel ];
+    virtualisation.additionalPaths = [ config.system.build.toplevel ];
+
+    virtualisation.sharedDirectories = {
+      nix-store = mkIf (!cfg.useNixStoreImage) {
+        source = builtins.storeDir;
+        target = "/nix/store";
+      };
+      xchg = {
+        source = ''"$TMPDIR"/xchg'';
+        target = "/tmp/xchg";
+      };
+      shared = {
+        source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"'';
+        target = "/tmp/shared";
+      };
+    };
+
+    virtualisation.qemu.networkingOptions =
+      let
+        forwardingOptions = flip concatMapStrings cfg.forwardPorts
+          ({ proto, from, host, guest }:
+            if from == "host"
+              then "hostfwd=${proto}:${host.address}:${toString host.port}-" +
+                   "${guest.address}:${toString guest.port},"
+              else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" +
+                   "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
+          );
+      in
+      [
+        "-net nic,netdev=user.0,model=virtio"
+        "-netdev user,id=user.0,${forwardingOptions}\"$QEMU_NET_OPTS\""
+      ];
 
     # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
-      (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
+      (mkIf pkgs.stdenv.hostPlatform.isx86 [
         "-usb" "-device usb-tablet,bus=usb-bus.0"
       ])
       (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
@@ -632,7 +845,7 @@ 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 [
-        "-drive if=pflash,format=raw,unit=0,readonly,file=${efiFirmware}"
+        "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
         "-drive if=pflash,format=raw,unit=1,file=$NIX_EFI_VARS"
       ])
       (mkIf (cfg.bios != null) [
@@ -646,16 +859,21 @@ in
     virtualisation.qemu.drives = mkMerge [
       [{
         name = "root";
-        file = "$NIX_DISK_IMAGE";
+        file = ''"$NIX_DISK_IMAGE"'';
         driveExtraOpts.cache = "writeback";
         driveExtraOpts.werror = "report";
       }]
+      (mkIf cfg.useNixStoreImage [{
+        name = "nix-store";
+        file = ''"$TMPDIR"/store.img'';
+        deviceExtraOpts.bootindex = if cfg.useBootLoader then "3" else "2";
+      }])
       (mkIf cfg.useBootLoader [
         # The order of this list determines the device names, see
         # note [Disk layout with `useBootLoader`].
         {
           name = "boot";
-          file = "$TMPDIR/disk.img";
+          file = ''"$TMPDIR"/disk.img'';
           driveExtraOpts.media = "disk";
           deviceExtraOpts.bootindex = "1";
         }
@@ -672,48 +890,54 @@ in
     # configuration, where the regular value for the `fileSystems'
     # attribute should be disregarded for the purpose of building a VM
     # test image (since those filesystems don't exist in the VM).
-    fileSystems = mkVMOverride (
-      cfg.fileSystems //
-      { "/".device = cfg.bootDevice;
-        ${if cfg.writableStore then "/nix/.ro-store" else "/nix/store"} =
-          { device = "store";
-            fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
-            neededForBoot = true;
-          };
+    fileSystems =
+    let
+      mkSharedDir = tag: share:
+        {
+          name =
+            if tag == "nix-store" && cfg.writableStore
+              then "/nix/.ro-store"
+              else share.target;
+          value.device = tag;
+          value.fsType = "9p";
+          value.neededForBoot = true;
+          value.options =
+            [ "trans=virtio" "version=9p2000.L"  "msize=${toString cfg.msize}" ]
+            ++ lib.optional (tag == "nix-store") "cache=loose";
+        };
+    in
+      mkVMOverride (cfg.fileSystems //
+      {
+        "/".device = cfg.bootDevice;
+
         "/tmp" = mkIf config.boot.tmpOnTmpfs
           { device = "tmpfs";
             fsType = "tmpfs";
             neededForBoot = true;
             # Sync with systemd's tmp.mount;
-            options = [ "mode=1777" "strictatime" "nosuid" "nodev" ];
-          };
-        "/tmp/xchg" =
-          { device = "xchg";
-            fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
-            neededForBoot = true;
-          };
-        "/tmp/shared" =
-          { device = "shared";
-            fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
-            neededForBoot = true;
+            options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
           };
-      } // optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs)
-      { "/nix/.rw-store" =
+
+        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" =
+          mkIf cfg.useNixStoreImage
+            { device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
+              neededForBoot = true;
+              options = [ "ro" ];
+            };
+
+        "/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs)
           { fsType = "tmpfs";
             options = [ "mode=0755" ];
             neededForBoot = true;
           };
-      } // optionalAttrs cfg.useBootLoader
-      { "/boot" =
+
+        "/boot" = mkIf cfg.useBootLoader
           # see note [Disk layout with `useBootLoader`]
           { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
             fsType = "vfat";
             noCheck = true; # fsck fails on a r/o filesystem
           };
-      });
+      } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
 
     swapDevices = mkVMOverride [ ];
     boot.initrd.luks.devices = mkVMOverride {};
@@ -734,7 +958,7 @@ in
     # video driver the host uses.
     services.xserver.videoDrivers = mkVMOverride [ "modesetting" ];
     services.xserver.defaultDepth = mkVMOverride 0;
-    services.xserver.resolutions = mkVMOverride [ { x = 1024; y = 768; } ];
+    services.xserver.resolutions = mkVMOverride [ cfg.resolution ];
     services.xserver.monitorSection =
       ''
         # Set a higher refresh rate so that resolutions > 800x600 work.
diff --git a/nixos/modules/virtualisation/railcar.nix b/nixos/modules/virtualisation/railcar.nix
index b603effef6e01..e719e25650d37 100644
--- a/nixos/modules/virtualisation/railcar.nix
+++ b/nixos/modules/virtualisation/railcar.nix
@@ -41,7 +41,7 @@ let
         description = "Source for the in-container mount";
       };
       options = mkOption {
-        type = attrsOf (str);
+        type = listOf str;
         default = [ "bind" ];
         description = ''
           Mount options of the filesystem to be used.
@@ -77,9 +77,7 @@ in
               The defaults have been chosen for simple bindmounts, meaning
               that you only need to provide the "source" parameter.
             '';
-            example = ''
-              { "/data" = { source = "/var/lib/data"; }; }
-            '';
+            example = { "/data" = { source = "/var/lib/data"; }; };
           };
 
           runType = mkOption {
@@ -112,6 +110,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.railcar;
+      defaultText = literalExpression "pkgs.railcar";
       description = "Railcar package to use";
     };
   };
diff --git a/nixos/modules/virtualisation/spice-usb-redirection.nix b/nixos/modules/virtualisation/spice-usb-redirection.nix
index 4168cebe79b18..255327f2622c9 100644
--- a/nixos/modules/virtualisation/spice-usb-redirection.nix
+++ b/nixos/modules/virtualisation/spice-usb-redirection.nix
@@ -14,9 +14,11 @@
 
   config = lib.mkIf config.virtualisation.spiceUSBRedirection.enable {
     environment.systemPackages = [ pkgs.spice-gtk ]; # For polkit actions
-    security.wrappers.spice-client-glib-usb-acl-helper ={
-      source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper";
+    security.wrappers.spice-client-glib-usb-acl-helper = {
+      owner = "root";
+      group = "root";
       capabilities = "cap_fowner+ep";
+      source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper";
     };
   };
 
diff --git a/nixos/modules/virtualisation/virtualbox-guest.nix b/nixos/modules/virtualisation/virtualbox-guest.nix
index 486951983d303..f702fb4e525c2 100644
--- a/nixos/modules/virtualisation/virtualbox-guest.nix
+++ b/nixos/modules/virtualisation/virtualbox-guest.nix
@@ -33,7 +33,7 @@ in
 
   config = mkIf cfg.enable (mkMerge [{
     assertions = [{
-      assertion = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+      assertion = pkgs.stdenv.hostPlatform.isx86;
       message = "Virtualbox not currently supported on ${pkgs.stdenv.hostPlatform.system}";
     }];
 
diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index ddb0a7bda4f34..2acf54aae2ef6 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixos/modules/virtualisation/virtualbox-host.nix
@@ -6,7 +6,7 @@ let
   cfg = config.virtualisation.virtualbox.host;
 
   virtualbox = cfg.package.override {
-    inherit (cfg) enableHardening headless;
+    inherit (cfg) enableHardening headless enableWebService;
     extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null;
   };
 
@@ -43,7 +43,7 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.virtualbox;
-      defaultText = "pkgs.virtualbox";
+      defaultText = literalExpression "pkgs.virtualbox";
       description = ''
         Which VirtualBox package to use.
       '';
@@ -80,6 +80,14 @@ in
         and when virtual machines are controlled only via SSH.
       '';
     };
+
+    enableWebService = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Build VirtualBox web service tool (vboxwebsrv) to allow managing VMs via other webpage frontend tools. Useful for headless servers.
+      '';
+    };
   };
 
   config = mkIf cfg.enable (mkMerge [{
diff --git a/nixos/modules/virtualisation/vmware-guest.nix b/nixos/modules/virtualisation/vmware-guest.nix
index 9465a8d6800d4..09b26eeb33a92 100644
--- a/nixos/modules/virtualisation/vmware-guest.nix
+++ b/nixos/modules/virtualisation/vmware-guest.nix
@@ -23,7 +23,7 @@ in
 
   config = mkIf cfg.enable {
     assertions = [ {
-      assertion = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
+      assertion = pkgs.stdenv.hostPlatform.isx86;
       message = "VMWare guest is not currently supported on ${pkgs.stdenv.hostPlatform.system}";
     } ];
 
@@ -37,6 +37,28 @@ in
         serviceConfig.ExecStart = "${open-vm-tools}/bin/vmtoolsd";
       };
 
+    # Mount the vmblock for drag-and-drop and copy-and-paste.
+    systemd.mounts = mkIf (!cfg.headless) [
+      {
+        description = "VMware vmblock fuse mount";
+        documentation = [ "https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/vmblock-fuse/design.txt" ];
+        before = [ "vmware.service" ];
+        wants = [ "vmware.service" ];
+        what = "${open-vm-tools}/bin/vmware-vmblock-fuse";
+        where = "/run/vmblock-fuse";
+        type = "fuse";
+        options = "subtype=vmware-vmblock,default_permissions,allow_other";
+        wantedBy = [ "multi-user.target" ];
+      }
+    ];
+
+    security.wrappers.vmware-user-suid-wrapper = mkIf (!cfg.headless) {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${open-vm-tools}/bin/vmware-user-suid-wrapper";
+      };
+
     environment.etc.vmware-tools.source = "${open-vm-tools}/etc/vmware-tools/*";
 
     services.xserver = mkIf (!cfg.headless) {
diff --git a/nixos/modules/virtualisation/waydroid.nix b/nixos/modules/virtualisation/waydroid.nix
new file mode 100644
index 0000000000000..854ab056dbb84
--- /dev/null
+++ b/nixos/modules/virtualisation/waydroid.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.virtualisation.waydroid;
+  kernelPackages = config.boot.kernelPackages;
+  waydroidGbinderConf = pkgs.writeText "waydroid.conf" ''
+    [Protocol]
+    /dev/binder = aidl2
+    /dev/vndbinder = aidl2
+    /dev/hwbinder = hidl
+
+    [ServiceManager]
+    /dev/binder = aidl2
+    /dev/vndbinder = aidl2
+    /dev/hwbinder = hidl
+  '';
+
+in {
+
+  options.virtualisation.waydroid = {
+    enable = mkEnableOption "Waydroid";
+  };
+
+  config = mkIf cfg.enable {
+    assertions = singleton {
+      assertion = versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.18";
+      message = "Waydroid needs user namespace support to work properly";
+    };
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isEnabled "ANDROID_BINDER_IPC")
+      (isEnabled "ANDROID_BINDERFS")
+      (isEnabled "ASHMEM")
+    ];
+
+    environment.etc."gbinder.d/waydroid.conf".source = waydroidGbinderConf;
+
+    environment.systemPackages = with pkgs; [ waydroid ];
+
+    networking.firewall.trustedInterfaces = [ "waydroid0" ];
+
+    virtualisation.lxc.enable = true;
+
+    systemd.services.waydroid-container = {
+      description = "Waydroid Container";
+
+      wantedBy = [ "multi-user.target" ];
+
+      path = with pkgs; [ getent iptables iproute kmod nftables util-linux which ];
+
+      unitConfig = {
+        ConditionPathExists = "/var/lib/waydroid/lxc/waydroid";
+      };
+
+      serviceConfig = {
+        ExecStart = "${pkgs.waydroid}/bin/waydroid container start";
+        ExecStop = "${pkgs.waydroid}/bin/waydroid container stop";
+        ExecStopPost = "${pkgs.waydroid}/bin/waydroid session stop";
+      };
+    };
+  };
+
+}
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index fea43727f2fb3..f8f4af4f6b850 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -35,8 +35,8 @@ in
 
     virtualisation.xen.package = mkOption {
       type = types.package;
-      defaultText = "pkgs.xen";
-      example = literalExample "pkgs.xen-light";
+      defaultText = literalExpression "pkgs.xen";
+      example = literalExpression "pkgs.xen-light";
       description = ''
         The package used for Xen binary.
       '';
@@ -45,8 +45,8 @@ in
 
     virtualisation.xen.package-qemu = mkOption {
       type = types.package;
-      defaultText = "pkgs.xen";
-      example = literalExample "pkgs.qemu_xen-light";
+      defaultText = literalExpression "pkgs.xen";
+      example = literalExpression "pkgs.qemu_xen-light";
       description = ''
         The package with qemu binaries for dom0 qemu and xendomains.
       '';
diff --git a/nixos/release.nix b/nixos/release.nix
index 264d82bacc8a1..6b7564a9b9721 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -251,6 +251,37 @@ in rec {
 
   );
 
+  # An image that can be imported into lxd and used for container creation
+  lxdImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
+    with import ./.. { inherit system; };
+
+    hydraJob ((import lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ configuration
+          versionModule
+          ./maintainers/scripts/lxd/lxd-image.nix
+        ];
+    }).config.system.build.tarball)
+
+  );
+
+  # Metadata for the lxd image
+  lxdMeta = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
+    with import ./.. { inherit system; };
+
+    hydraJob ((import lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ configuration
+          versionModule
+          ./maintainers/scripts/lxd/lxd-image.nix
+        ];
+    }).config.system.build.metadata)
+
+  );
 
   # Ensure that all packages used by the minimal NixOS config end up in the channel.
   dummy = forAllSystems (system: pkgs.runCommand "dummy"
diff --git a/nixos/tests/airsonic.nix b/nixos/tests/airsonic.nix
index 59bd84877c61c..d8df092c2ecfa 100644
--- a/nixos/tests/airsonic.nix
+++ b/nixos/tests/airsonic.nix
@@ -11,10 +11,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         enable = true;
         maxMemory = 800;
       };
-
-      # Airsonic is a Java application, and unfortunately requires a significant
-      # amount of memory.
-      virtualisation.memorySize = 1024;
     };
 
   testScript = ''
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 6baa986b2bda3..b8219416dc42a 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -26,17 +26,14 @@ in
   agda = handleTest ./agda.nix {};
   airsonic = handleTest ./airsonic.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.nix {};
-  ammonite = handleTest ./ammonite.nix {};
   apparmor = handleTest ./apparmor.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   avahi = handleTest ./avahi.nix {};
   avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
-  awscli = handleTest ./awscli.nix { };
   babeld = handleTest ./babeld.nix {};
-  bat = handleTest ./bat.nix {};
   bazarr = handleTest ./bazarr.nix {};
-  bcachefs = handleTestOn ["x86_64-linux"] ./bcachefs.nix {}; # linux-4.18.2018.10.12 is unsupported on aarch64
+  bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
   beanstalkd = handleTest ./beanstalkd.nix {};
   bees = handleTest ./bees.nix {};
   bind = handleTest ./bind.nix {};
@@ -97,7 +94,9 @@ in
   cryptpad = handleTest ./cryptpad.nix {};
   deluge = handleTest ./deluge.nix {};
   dendrite = handleTest ./dendrite.nix {};
+  dex-oidc = handleTest ./dex-oidc.nix {};
   dhparams = handleTest ./dhparams.nix {};
+  disable-installer-tools = handleTest ./disable-installer-tools.nix {};
   discourse = handleTest ./discourse.nix {};
   dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
   dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {};
@@ -110,6 +109,7 @@ in
   docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
   documize = handleTest ./documize.nix {};
   dokuwiki = handleTest ./dokuwiki.nix {};
+  domination = handleTest ./domination.nix {};
   dovecot = handleTest ./dovecot.nix {};
   ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
   ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
@@ -129,7 +129,6 @@ in
   ferm = handleTest ./ferm.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
   firefox-esr    = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
-  firefox-esr-78 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-78; };
   firefox-esr-91 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-91; };
   firejail = handleTest ./firejail.nix {};
   firewall = handleTest ./firewall.nix {};
@@ -163,6 +162,7 @@ in
   grocy = handleTest ./grocy.nix {};
   grub = handleTest ./grub.nix {};
   gvisor = handleTest ./gvisor.nix {};
+  hadoop.all = handleTestOn [ "x86_64-linux" ] ./hadoop/hadoop.nix {};
   hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {};
   hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {};
   handbrake = handleTestOn ["x86_64-linux"] ./handbrake.nix {};
@@ -171,7 +171,9 @@ in
   hedgedoc = handleTest ./hedgedoc.nix {};
   herbstluftwm = handleTest ./herbstluftwm.nix {};
   installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
+  invidious = handleTest ./invidious.nix {};
   oci-containers = handleTestOn ["x86_64-linux"] ./oci-containers.nix {};
+  odoo = handleTest ./odoo.nix {};
   # 9pnet_virtio used to mount /nix partition doesn't support
   # hibernation. This test happens to work on x86_64-linux but
   # not on other platforms.
@@ -188,7 +190,7 @@ in
   i3wm = handleTest ./i3wm.nix {};
   icingaweb2 = handleTest ./icingaweb2.nix {};
   iftop = handleTest ./iftop.nix {};
-  ihatemoney = handleTest ./ihatemoney.nix {};
+  ihatemoney = handleTest ./ihatemoney {};
   incron = handleTest ./incron.nix {};
   influxdb = handleTest ./influxdb.nix {};
   initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
@@ -204,6 +206,7 @@ in
   jackett = handleTest ./jackett.nix {};
   jellyfin = handleTest ./jellyfin.nix {};
   jenkins = handleTest ./jenkins.nix {};
+  jibri = handleTest ./jibri.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
   k3s = handleTest ./k3s.nix {};
@@ -216,27 +219,30 @@ in
   kerberos = handleTest ./kerberos/default.nix {};
   kernel-generic = handleTest ./kernel-generic.nix {};
   kernel-latest-ath-user-regd = handleTest ./kernel-latest-ath-user-regd.nix {};
+  kexec = handleTest ./kexec.nix {};
   keycloak = discoverTests (import ./keycloak.nix);
   keymap = handleTest ./keymap.nix {};
   knot = handleTest ./knot.nix {};
   krb5 = discoverTests (import ./krb5 {});
   ksm = handleTest ./ksm.nix {};
   kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
-  latestKernel.hardened = handleTest ./hardened.nix { latestKernel = true; };
   latestKernel.login = handleTest ./login.nix { latestKernel = true; };
   leaps = handleTest ./leaps.nix {};
+  libinput = handleTest ./libinput.nix {};
   libreddit = handleTest ./libreddit.nix {};
-  lidarr = handleTest ./lidarr.nix {};
+  libresprite = handleTest ./libresprite.nix {};
   libreswan = handleTest ./libreswan.nix {};
+  lidarr = handleTest ./lidarr.nix {};
   lightdm = handleTest ./lightdm.nix {};
   limesurvey = handleTest ./limesurvey.nix {};
   litestream = handleTest ./litestream.nix {};
   locate = handleTest ./locate.nix {};
   login = handleTest ./login.nix {};
   loki = handleTest ./loki.nix {};
-  lsd = handleTest ./lsd.nix {};
   lxd = handleTest ./lxd.nix {};
+  lxd-image = handleTest ./lxd-image.nix {};
   lxd-nftables = handleTest ./lxd-nftables.nix {};
+  lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
@@ -249,6 +255,7 @@ in
   matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {};
   matrix-synapse = handleTest ./matrix-synapse.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
+  meilisearch = handleTest ./meilisearch.nix {};
   memcached = handleTest ./memcached.nix {};
   metabase = handleTest ./metabase.nix {};
   minecraft = handleTest ./minecraft.nix {};
@@ -257,6 +264,7 @@ in
   miniflux = handleTest ./miniflux.nix {};
   minio = handleTest ./minio.nix {};
   misc = handleTest ./misc.nix {};
+  mjolnir = handleTest ./matrix/mjolnir.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   moinmoin = handleTest ./moinmoin.nix {};
   mongodb = handleTest ./mongodb.nix {};
@@ -309,6 +317,7 @@ in
   nitter = handleTest ./nitter.nix {};
   nix-serve = handleTest ./nix-ssh-serve.nix {};
   nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
+  nixops = handleTest ./nixops/default.nix {};
   nixos-generate-config = handleTest ./nixos-generate-config.nix {};
   node-red = handleTest ./node-red.nix {};
   nomad = handleTest ./nomad.nix {};
@@ -320,12 +329,14 @@ in
   ombi = handleTest ./ombi.nix {};
   openarena = handleTest ./openarena.nix {};
   openldap = handleTest ./openldap.nix {};
+  openresty-lua = handleTest ./openresty-lua.nix {};
   opensmtpd = handleTest ./opensmtpd.nix {};
   opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {};
   openssh = handleTest ./openssh.nix {};
   openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {};
   openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
   opentabletdriver = handleTest ./opentabletdriver.nix {};
+  owncast = handleTest ./owncast.nix {};
   image-contents = handleTest ./image-contents.nix {};
   orangefs = handleTest ./orangefs.nix {};
   os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {};
@@ -334,10 +345,13 @@ in
   packagekit = handleTest ./packagekit.nix {};
   pam-oath-login = handleTest ./pam-oath-login.nix {};
   pam-u2f = handleTest ./pam-u2f.nix {};
+  pantalaimon = handleTest ./matrix/pantalaimon.nix {};
   pantheon = handleTest ./pantheon.nix {};
   paperless-ng = handleTest ./paperless-ng.nix {};
+  parsedmarc = handleTest ./parsedmarc {};
   pdns-recursor = handleTest ./pdns-recursor.nix {};
   peerflix = handleTest ./peerflix.nix {};
+  peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
   php = handleTest ./php {};
@@ -345,6 +359,7 @@ in
   php80 = handleTest ./php { php = pkgs.php80; };
   pinnwand = handleTest ./pinnwand.nix {};
   plasma5 = handleTest ./plasma5.nix {};
+  plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
   plausible = handleTest ./plausible.nix {};
   pleroma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./pleroma.nix {};
   plikd = handleTest ./plikd.nix {};
@@ -361,6 +376,7 @@ in
   postgresql = handleTest ./postgresql.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
   powerdns = handleTest ./powerdns.nix {};
+  power-profiles-daemon = handleTest ./power-profiles-daemon.nix {};
   pppd = handleTest ./pppd.nix {};
   predictable-interface-names = handleTest ./predictable-interface-names.nix {};
   printing = handleTest ./printing.nix {};
@@ -371,26 +387,29 @@ in
   prosody = handleTest ./xmpp/prosody.nix {};
   prosodyMysql = handleTest ./xmpp/prosody-mysql.nix {};
   proxy = handleTest ./proxy.nix {};
+  prowlarr = handleTest ./prowlarr.nix {};
   pt2-clone = handleTest ./pt2-clone.nix {};
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
   quorum = handleTest ./quorum.nix {};
   rabbitmq = handleTest ./rabbitmq.nix {};
   radarr = handleTest ./radarr.nix {};
   radicale = handleTest ./radicale.nix {};
+  rasdaemon = handleTest ./rasdaemon.nix {};
   redis = handleTest ./redis.nix {};
   redmine = handleTest ./redmine.nix {};
+  restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
   restic = handleTest ./restic.nix {};
   robustirc-bridge = handleTest ./robustirc-bridge.nix {};
   roundcube = handleTest ./roundcube.nix {};
   rspamd = handleTest ./rspamd.nix {};
   rss2email = handleTest ./rss2email.nix {};
   rsyslogd = handleTest ./rsyslogd.nix {};
-  runInMachine = handleTest ./run-in-machine.nix {};
   rxe = handleTest ./rxe.nix {};
   samba = handleTest ./samba.nix {};
   samba-wsdd = handleTest ./samba-wsdd.nix {};
   sanoid = handleTest ./sanoid.nix {};
   sddm = handleTest ./sddm.nix {};
+  seafile = handleTest ./seafile.nix {};
   searx = handleTest ./searx.nix {};
   service-runner = handleTest ./service-runner.nix {};
   shadow = handleTest ./shadow.nix {};
@@ -408,7 +427,7 @@ in
   solr = handleTest ./solr.nix {};
   sonarr = handleTest ./sonarr.nix {};
   spacecookie = handleTest ./spacecookie.nix {};
-  spike = handleTest ./spike.nix {};
+  spark = handleTestOn ["x86_64-linux"] ./spark {};
   sslh = handleTest ./sslh.nix {};
   sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
   sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {};
@@ -425,6 +444,7 @@ in
   systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
   systemd-boot = handleTest ./systemd-boot.nix {};
   systemd-confinement = handleTest ./systemd-confinement.nix {};
+  systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
   systemd-journal = handleTest ./systemd-journal.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
   systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {};
@@ -441,8 +461,8 @@ in
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
   tor = handleTest ./tor.nix {};
-  # traefik test relies on docker-containers
   trac = handleTest ./trac.nix {};
+  # traefik test relies on docker-containers
   traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {};
   trafficserver = handleTest ./trafficserver.nix {};
   transmission = handleTest ./transmission.nix {};
@@ -454,12 +474,12 @@ in
   turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {};
   tuxguitar = handleTest ./tuxguitar.nix {};
   ucarp = handleTest ./ucarp.nix {};
-  ucg = handleTest ./ucg.nix {};
   udisks2 = handleTest ./udisks2.nix {};
   unbound = handleTest ./unbound.nix {};
   unit-php = handleTest ./web-servers/unit-php.nix {};
   upnp = handleTest ./upnp.nix {};
   usbguard = handleTest ./usbguard.nix {};
+  user-activation-scripts = handleTest ./user-activation-scripts.nix {};
   uwsgi = handleTest ./uwsgi.nix {};
   v2ray = handleTest ./v2ray.nix {};
   vault = handleTest ./vault.nix {};
@@ -469,11 +489,13 @@ in
   victoriametrics = handleTest ./victoriametrics.nix {};
   vikunja = handleTest ./vikunja.nix {};
   virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
-  vscodium = handleTest ./vscodium.nix {};
+  vscodium = discoverTests (import ./vscodium.nix);
   wasabibackend = handleTest ./wasabibackend.nix {};
   wiki-js = handleTest ./wiki-js.nix {};
   wireguard = handleTest ./wireguard {};
+  without-nix = handleTest ./without-nix.nix {};
   wmderland = handleTest ./wmderland.nix {};
+  wpa_supplicant = handleTest ./wpa_supplicant.nix {};
   wordpress = handleTest ./wordpress.nix {};
   xandikos = handleTest ./xandikos.nix {};
   xautolock = handleTest ./xautolock.nix {};
@@ -484,7 +506,6 @@ in
   xterm = handleTest ./xterm.nix {};
   yabar = handleTest ./yabar.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
-  yq = handleTest ./yq.nix {};
   zfs = handleTest ./zfs.nix {};
   zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
   zoneminder = handleTest ./zoneminder.nix {};
diff --git a/nixos/tests/ammonite.nix b/nixos/tests/ammonite.nix
deleted file mode 100644
index 4b674f35e3cb0..0000000000000
--- a/nixos/tests/ammonite.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "ammonite";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  nodes = {
-    amm =
-      { pkgs, ... }:
-        {
-          environment.systemPackages = [ (pkgs.ammonite.override { jre = pkgs.jre8; }) ];
-        };
-    };
-
-  testScript = ''
-    start_all()
-
-    amm.succeed("amm -c 'val foo = 21; println(foo * 2)' | grep 42")
-  '';
-})
diff --git a/nixos/tests/atop.nix b/nixos/tests/atop.nix
index 1f8b005041f0b..f7a90346f3d74 100644
--- a/nixos/tests/atop.nix
+++ b/nixos/tests/atop.nix
@@ -105,8 +105,6 @@ let assertions = rec {
 };
 in
 {
-  name = "atop";
-
   justThePackage = makeTest {
     name = "atop-justThePackage";
     machine = {
diff --git a/nixos/tests/awscli.nix b/nixos/tests/awscli.nix
deleted file mode 100644
index e6741fcf14121..0000000000000
--- a/nixos/tests/awscli.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "awscli";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus ];
-  };
-
-  machine = { pkgs, ... }:
-    {
-      environment.systemPackages = [ pkgs.awscli ];
-    };
-
-  testScript =
-    ''
-      assert "${pkgs.python3Packages.botocore.version}" in machine.succeed("aws --version")
-      assert "${pkgs.awscli.version}" in machine.succeed("aws --version")
-    '';
-})
diff --git a/nixos/tests/bat.nix b/nixos/tests/bat.nix
deleted file mode 100644
index 0f548a590fb02..0000000000000
--- a/nixos/tests/bat.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "bat";
-  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
-
-  machine = { pkgs, ... }: { environment.systemPackages = [ pkgs.bat ]; };
-
-  testScript = ''
-    machine.succeed("echo 'Foobar\n\n\n42' > /tmp/foo")
-    assert "Foobar" in machine.succeed("bat -p /tmp/foo")
-    assert "42" in machine.succeed("bat -p /tmp/foo -r 4:4")
-  '';
-})
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index ee7a582922ce7..11420cba9dcec 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -26,7 +26,7 @@ let
       enable = true;
       settings = {
         dht-enabled = false;
-        message-level = 3;
+        message-level = 2;
         inherit download-dir;
       };
     };
diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix
index bdae6341ec91f..9945a1dcd62f7 100644
--- a/nixos/tests/boot.nix
+++ b/nixos/tests/boot.nix
@@ -4,10 +4,10 @@
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
-with import ../lib/qemu-flags.nix { inherit pkgs; };
 with pkgs.lib;
 
 let
+  qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
 
   iso =
     (import ../lib/eval-config.nix {
@@ -23,7 +23,7 @@ let
   makeBootTest = name: extraConfig:
     let
       machineConfig = pythonDict ({
-        qemuBinary = qemuBinary pkgs.qemu_test;
+        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
         qemuFlags = "-m 768";
       } // extraConfig);
     in
@@ -36,7 +36,7 @@ let
             machine = create_machine(${machineConfig})
             machine.start()
             machine.wait_for_unit("multi-user.target")
-            machine.succeed("nix verify -r --no-trust /run/current-system")
+            machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system")
 
             with subtest("Check whether the channel got installed correctly"):
                 machine.succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello")
@@ -65,7 +65,7 @@ let
         ];
       };
       machineConfig = pythonDict ({
-        qemuBinary = qemuBinary pkgs.qemu_test;
+        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
         qemuFlags = "-boot order=n -m 2000";
         netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
       } // extraConfig);
diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix
index fae1d2d071389..cbb28689209b8 100644
--- a/nixos/tests/borgbackup.nix
+++ b/nixos/tests/borgbackup.nix
@@ -81,6 +81,24 @@ in {
           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
         };
 
+        commandSuccess = {
+          dumpCommand = pkgs.writeScript "commandSuccess" ''
+            echo -n test
+          '';
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+        commandFail = {
+          dumpCommand = "${pkgs.coreutils}/bin/false";
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
       };
     };
 
@@ -171,5 +189,20 @@ in {
         client.fail("{} list borg\@server:wrong".format(borg))
 
         # TODO: Make sure that data is not actually deleted
+
+    with subtest("commandSuccess"):
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.systemctl("start --wait borgbackup-job-commandSuccess")
+        client.fail("systemctl is-failed borgbackup-job-commandSuccess")
+        id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
+        client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
+        assert "test" == client.succeed("cat stdin")
+
+    with subtest("commandFail"):
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.systemctl("start --wait borgbackup-job-commandFail")
+        client.succeed("systemctl is-failed borgbackup-job-commandFail")
   '';
 })
diff --git a/nixos/tests/cage.nix b/nixos/tests/cage.nix
index e6bef374d3037..83bae3deeeab2 100644
--- a/nixos/tests/cage.nix
+++ b/nixos/tests/cage.nix
@@ -17,7 +17,6 @@ import ./make-test-python.nix ({ pkgs, ...} :
       program = "${pkgs.xterm}/bin/xterm -cm -pc -fa Monospace -fs 24";
     };
 
-    virtualisation.memorySize = 1024;
     # Need to switch to a different GPU driver than the default one (-vga std) so that Cage can launch:
     virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
   };
diff --git a/nixos/tests/cagebreak.nix b/nixos/tests/cagebreak.nix
index 242e59f5d7aba..c6c2c632b61ac 100644
--- a/nixos/tests/cagebreak.nix
+++ b/nixos/tests/cagebreak.nix
@@ -35,7 +35,6 @@ in
     programs.xwayland.enable = true;
     environment.systemPackages = [ pkgs.cagebreak pkgs.wayland-utils ];
 
-    virtualisation.memorySize = 1024;
     # Need to switch to a different GPU driver than the default one (-vga std) so that Cagebreak can launch:
     virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
   };
diff --git a/nixos/tests/calibre-web.nix b/nixos/tests/calibre-web.nix
index 0af997317fcd4..9832d54697872 100644
--- a/nixos/tests/calibre-web.nix
+++ b/nixos/tests/calibre-web.nix
@@ -11,10 +11,6 @@ import ./make-test-python.nix (
         meta.maintainers = with pkgs.lib.maintainers; [ pborzenkov ];
 
         nodes = {
-          default = { ... }: {
-            services.calibre-web.enable = true;
-          };
-
           customized = { pkgs, ... }: {
             services.calibre-web = {
               enable = true;
@@ -33,12 +29,6 @@ import ./make-test-python.nix (
         testScript = ''
           start_all()
 
-          default.wait_for_unit("calibre-web.service")
-          default.wait_for_open_port(${toString defaultPort})
-          default.succeed(
-              "curl --fail 'http://localhost:${toString defaultPort}/basicconfig' | grep 'Basic Configuration'"
-          )
-
           customized.succeed(
               "mkdir /tmp/books && calibredb --library-path /tmp/books add -e --title test-book"
           )
diff --git a/nixos/tests/cassandra.nix b/nixos/tests/cassandra.nix
index bef3105f0a9eb..a19d525c3431e 100644
--- a/nixos/tests/cassandra.nix
+++ b/nixos/tests/cassandra.nix
@@ -41,7 +41,6 @@ let
       ];
     };
     services.cassandra = cassandraCfg ipAddress // extra;
-    virtualisation.memorySize = 1024;
   };
 in
 {
diff --git a/nixos/tests/ceph-multi-node.nix b/nixos/tests/ceph-multi-node.nix
index 33736e27b984d..29e7c279d69ac 100644
--- a/nixos/tests/ceph-multi-node.nix
+++ b/nixos/tests/ceph-multi-node.nix
@@ -37,7 +37,6 @@ let
 
   generateHost = { pkgs, cephConfig, networkConfig, ... }: {
     virtualisation = {
-      memorySize = 1024;
       emptyDiskImages = [ 20480 ];
       vlans = [ 1 ];
     };
diff --git a/nixos/tests/ceph-single-node-bluestore.nix b/nixos/tests/ceph-single-node-bluestore.nix
index f706d4d56fcfd..acaae4cf300e8 100644
--- a/nixos/tests/ceph-single-node-bluestore.nix
+++ b/nixos/tests/ceph-single-node-bluestore.nix
@@ -34,7 +34,6 @@ let
 
   generateHost = { pkgs, cephConfig, networkConfig, ... }: {
     virtualisation = {
-      memorySize = 1024;
       emptyDiskImages = [ 20480 20480 20480 ];
       vlans = [ 1 ];
     };
diff --git a/nixos/tests/ceph-single-node.nix b/nixos/tests/ceph-single-node.nix
index d1d56ea6708cc..4fe5dc59ff8f3 100644
--- a/nixos/tests/ceph-single-node.nix
+++ b/nixos/tests/ceph-single-node.nix
@@ -34,7 +34,6 @@ let
 
   generateHost = { pkgs, cephConfig, networkConfig, ... }: {
     virtualisation = {
-      memorySize = 1024;
       emptyDiskImages = [ 20480 20480 20480 ];
       vlans = [ 1 ];
     };
diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix
index ea9e19cefbc9f..8965646bc5dcf 100644
--- a/nixos/tests/chromium.nix
+++ b/nixos/tests/chromium.nix
@@ -80,9 +80,13 @@ mapAttrs (channel: chromiumPkg: makeTest rec {
             binary = pname
         # Add optional CLI options:
         options = []
+        major_version = "${versions.major (getVersion chromiumPkg.name)}"
+        if major_version > "95" and not pname.startswith("google-chrome"):
+            # Workaround to avoid a GPU crash:
+            options.append("--use-gl=swiftshader")
         # Launch the process:
         options.append("file://${startupHTML}")
-        machine.succeed(ru(f'ulimit -c unlimited; {binary} {shlex.join(options)} & disown'))
+        machine.succeed(ru(f'ulimit -c unlimited; {binary} {shlex.join(options)} >&2 & disown'))
         if binary.startswith("google-chrome"):
             # Need to click away the first window:
             machine.wait_for_text("Make Google Chrome the default browser")
@@ -211,7 +215,7 @@ mapAttrs (channel: chromiumPkg: makeTest rec {
 
         clipboard = machine.succeed(
             ru(
-                "echo void | ${pkgs.xclip}/bin/xclip -i"
+                "echo void | ${pkgs.xclip}/bin/xclip -i >&2"
             )
         )
         machine.succeed(
diff --git a/nixos/tests/cifs-utils.nix b/nixos/tests/cifs-utils.nix
deleted file mode 100644
index 98587b10d941a..0000000000000
--- a/nixos/tests/cifs-utils.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "cifs-utils";
-
-  machine = { pkgs, ... }: { environment.systemPackages = [ pkgs.cifs-utils ]; };
-
-  testScript = ''
-    machine.succeed("smbinfo -h")
-    machine.succeed("smb2-quota -h")
-    assert "${pkgs.cifs-utils.version}" in machine.succeed("cifs.upcall -v")
-    assert "${pkgs.cifs-utils.version}" in machine.succeed("mount.cifs -V")
-  '';
-})
diff --git a/nixos/tests/cntr.nix b/nixos/tests/cntr.nix
index 8cffd97459d00..668470756209a 100644
--- a/nixos/tests/cntr.nix
+++ b/nixos/tests/cntr.nix
@@ -9,7 +9,7 @@ let
     makeTest {
       name = "cntr-${backend}";
 
-      meta = { maintainers = with lib.maintainers; [ srk mic92 ]; };
+      meta = { maintainers = with lib.maintainers; [ sorki mic92 ]; };
 
       nodes = {
         ${backend} = { pkgs, ... }: {
diff --git a/nixos/tests/common/wayland-cage.nix b/nixos/tests/common/wayland-cage.nix
new file mode 100644
index 0000000000000..fd0700941392b
--- /dev/null
+++ b/nixos/tests/common/wayland-cage.nix
@@ -0,0 +1,13 @@
+{ ... }:
+
+{
+  imports = [ ./user-account.nix ];
+  services.cage = {
+    enable = true;
+    user = "alice";
+  };
+
+  virtualisation = {
+    qemu.options = [ "-vga virtio" ];
+  };
+}
diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix
index 12fa67c8b015b..b8661fd7997c9 100644
--- a/nixos/tests/containers-bridge.nix
+++ b/nixos/tests/containers-bridge.nix
@@ -15,7 +15,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
-      virtualisation.memorySize = 768;
 
       networking.bridges = {
         br0 = {
@@ -56,7 +55,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         };
 
 
-      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
     };
 
   testScript = ''
diff --git a/nixos/tests/containers-ephemeral.nix b/nixos/tests/containers-ephemeral.nix
index fabf0593f23af..db1631cf5b5d1 100644
--- a/nixos/tests/containers-ephemeral.nix
+++ b/nixos/tests/containers-ephemeral.nix
@@ -5,7 +5,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   };
 
   machine = { pkgs, ... }: {
-    virtualisation.memorySize = 768;
     virtualisation.writableStore = true;
 
     containers.webserver = {
diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix
index cbbb252583254..b8f3d9844064c 100644
--- a/nixos/tests/containers-extra_veth.nix
+++ b/nixos/tests/containers-extra_veth.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
-      virtualisation.memorySize = 768;
       virtualisation.vlans = [];
 
       networking.useDHCP = false;
@@ -45,7 +44,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
             };
         };
 
-      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
     };
 
   testScript =
diff --git a/nixos/tests/containers-hosts.nix b/nixos/tests/containers-hosts.nix
index 1f24ed1f3c2c4..3c6a15710027a 100644
--- a/nixos/tests/containers-hosts.nix
+++ b/nixos/tests/containers-hosts.nix
@@ -7,7 +7,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   machine =
     { lib, ... }:
     {
-      virtualisation.memorySize = 256;
       virtualisation.vlans = [];
 
       networking.bridges.br0.interfaces = [];
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 1dcccfc306a35..a126a5480c035 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -14,10 +14,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       nix.binaryCaches = []; # don't try to access cache.nixos.org
 
       virtualisation.writableStore = true;
-      virtualisation.memorySize = 1024;
       # Make sure we always have all the required dependencies for creating a
       # container available within the VM, because we don't have network access.
-      virtualisation.pathsInNixDB = let
+      virtualisation.additionalPaths = let
         emptyContainer = import ../lib/eval-config.nix {
           inherit (config.nixpkgs.localSystem) system;
           modules = lib.singleton {
@@ -119,7 +118,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
       with subtest("Stop a container early"):
           machine.succeed(f"nixos-container stop {id1}")
-          machine.succeed(f"nixos-container start {id1} &")
+          machine.succeed(f"nixos-container start {id1} >&2 &")
           machine.wait_for_console_text("Stage 2")
           machine.succeed(f"nixos-container stop {id1}")
           machine.wait_for_console_text(f"Container {id1} exited successfully")
diff --git a/nixos/tests/containers-ip.nix b/nixos/tests/containers-ip.nix
index 5abea2dbad9fa..91fdda0392a9b 100644
--- a/nixos/tests/containers-ip.nix
+++ b/nixos/tests/containers-ip.nix
@@ -22,12 +22,11 @@ in import ./make-test-python.nix ({ pkgs, lib, ... }: {
       imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation = {
         writableStore = true;
-        memorySize = 768;
       };
 
       containers.webserver4 = webserverFor "10.231.136.1" "10.231.136.2";
       containers.webserver6 = webserverFor "fc00::2" "fc00::1";
-      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
     };
 
   testScript = { nodes, ... }: ''
diff --git a/nixos/tests/containers-macvlans.nix b/nixos/tests/containers-macvlans.nix
index d0f41be8c1251..a0cea8db4a1ab 100644
--- a/nixos/tests/containers-macvlans.nix
+++ b/nixos/tests/containers-macvlans.nix
@@ -15,7 +15,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine1 =
       { lib, ... }:
       {
-        virtualisation.memorySize = 256;
         virtualisation.vlans = [ 1 ];
 
         # To be able to ping containers from the host, it is necessary
@@ -55,7 +54,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine2 =
       { ... }:
       {
-        virtualisation.memorySize = 256;
         virtualisation.vlans = [ 1 ];
       };
 
diff --git a/nixos/tests/containers-physical_interfaces.nix b/nixos/tests/containers-physical_interfaces.nix
index 57bd0eedcc335..e203f88786a3e 100644
--- a/nixos/tests/containers-physical_interfaces.nix
+++ b/nixos/tests/containers-physical_interfaces.nix
@@ -7,7 +7,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   nodes = {
     server = { ... }:
       {
-        virtualisation.memorySize = 256;
         virtualisation.vlans = [ 1 ];
 
         containers.server = {
@@ -23,7 +22,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         };
       };
     bridged = { ... }: {
-      virtualisation.memorySize = 128;
       virtualisation.vlans = [ 1 ];
 
       containers.bridged = {
@@ -41,7 +39,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     };
 
     bonded = { ... }: {
-      virtualisation.memorySize = 128;
       virtualisation.vlans = [ 1 ];
 
       containers.bonded = {
@@ -62,7 +59,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     };
 
     bridgedbond = { ... }: {
-      virtualisation.memorySize = 128;
       virtualisation.vlans = [ 1 ];
 
       containers.bridgedbond = {
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index 221a6f50efd1d..6cecd72f1bda3 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -15,7 +15,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
-      virtualisation.memorySize = 768;
 
       containers.webserver =
         { privateNetwork = true;
@@ -29,7 +28,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
             };
         };
 
-      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
     };
 
   testScript =
diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix
index fd9f9a252ca8c..d95178d1ff588 100644
--- a/nixos/tests/containers-tmpfs.nix
+++ b/nixos/tests/containers-tmpfs.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
-      virtualisation.memorySize = 768;
 
       containers.tmpfs =
         {
@@ -26,7 +25,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           config = { };
         };
 
-      virtualisation.pathsInNixDB = [ pkgs.stdenv ];
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
     };
 
   testScript = ''
diff --git a/nixos/tests/croc.nix b/nixos/tests/croc.nix
index 75a8fc991d47e..5d709eb3d1cb3 100644
--- a/nixos/tests/croc.nix
+++ b/nixos/tests/croc.nix
@@ -38,7 +38,7 @@ in {
     sender.execute("echo Hello World > testfile01.txt")
     sender.execute("echo Hello Earth > testfile02.txt")
     sender.execute(
-        "croc --pass ${pass} --relay relay send --code topSecret testfile01.txt testfile02.txt &"
+        "croc --pass ${pass} --relay relay send --code topSecret testfile01.txt testfile02.txt >&2 &"
     )
 
     # receive the testfiles and check them
diff --git a/nixos/tests/custom-ca.nix b/nixos/tests/custom-ca.nix
index 26f29a3e68fef..0ab49f3b34306 100644
--- a/nixos/tests/custom-ca.nix
+++ b/nixos/tests/custom-ca.nix
@@ -81,8 +81,6 @@ in
 
       # chromium-based browsers refuse to run as root
       test-support.displayManager.auto.user = "alice";
-      # browsers may hang with the default memory
-      virtualisation.memorySize = "500";
 
       networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
       security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
@@ -109,11 +107,9 @@ in
 
       environment.systemPackages = with pkgs; [
         xdotool
-        # Firefox was disabled here, because we needed to disable p11-kit support in nss,
-        # which is why it will not use the system certificate store for the time being.
-        # firefox
+        firefox
         chromium
-        falkon
+        qutebrowser
         midori
       ];
     };
@@ -152,21 +148,19 @@ in
     with subtest("Unknown CA is untrusted in curl"):
         machine.fail("curl -fv https://bad.example.com")
 
-    browsers = [
-      # Firefox was disabled here, because we needed to disable p11-kit support in nss,
-      # which is why it will not use the system certificate store for the time being.
-      # "firefox",
-      "chromium",
-      "falkon",
-      "midori"
-    ]
-    errors = ["Security Risk", "not private", "Certificate Error", "Security"]
+    browsers = {
+      "firefox": "Security Risk",
+      "chromium": "not private",
+      "qutebrowser -T": "Certificate error",
+      "midori": "Security"
+    }
 
     machine.wait_for_x()
-    for browser, error in zip(browsers, errors):
+    for command, error in browsers.items():
+        browser = command.split()[0]
         with subtest("Good certificate is trusted in " + browser):
             execute_as(
-                "alice", f"env P11_KIT_DEBUG=trust {browser} https://good.example.com & >&2"
+                "alice", f"env P11_KIT_DEBUG=trust {command} https://good.example.com & >&2"
             )
             wait_for_window_as("alice", browser)
             machine.wait_for_text("It works!")
@@ -174,7 +168,7 @@ in
             execute_as("alice", "xdotool key ctrl+w")  # close tab
 
         with subtest("Unknown CA is untrusted in " + browser):
-            execute_as("alice", f"{browser} https://bad.example.com & >&2")
+            execute_as("alice", f"{command} https://bad.example.com & >&2")
             machine.wait_for_text(error)
             machine.screenshot("bad" + browser)
             machine.succeed("pkill " + browser)
diff --git a/nixos/tests/deluge.nix b/nixos/tests/deluge.nix
index f673ec2db5a78..33c57ce7c36c8 100644
--- a/nixos/tests/deluge.nix
+++ b/nixos/tests/deluge.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   };
 
   nodes = {
-    simple2 = {
+    simple = {
       services.deluge = {
         enable = true;
         package = pkgs.deluge-2_x;
@@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       };
     };
 
-    declarative2 = {
+    declarative = {
       services.deluge = {
         enable = true;
         package = pkgs.deluge-2_x;
@@ -45,27 +45,16 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   testScript = ''
     start_all()
 
-    simple1.wait_for_unit("deluged")
-    simple2.wait_for_unit("deluged")
-    simple1.wait_for_unit("delugeweb")
-    simple2.wait_for_unit("delugeweb")
-    simple1.wait_for_open_port("8112")
-    simple2.wait_for_open_port("8112")
-    declarative1.wait_for_unit("network.target")
-    declarative2.wait_for_unit("network.target")
-    declarative1.wait_until_succeeds("curl --fail http://simple1:8112")
-    declarative2.wait_until_succeeds("curl --fail http://simple2:8112")
+    simple.wait_for_unit("deluged")
+    simple.wait_for_unit("delugeweb")
+    simple.wait_for_open_port("8112")
+    declarative.wait_for_unit("network.target")
+    declarative.wait_until_succeeds("curl --fail http://simple:8112")
 
-    declarative1.wait_for_unit("deluged")
-    declarative2.wait_for_unit("deluged")
-    declarative1.wait_for_unit("delugeweb")
-    declarative2.wait_for_unit("delugeweb")
-    declarative1.wait_until_succeeds("curl --fail http://declarative1:3142")
-    declarative2.wait_until_succeeds("curl --fail http://declarative2:3142")
-    declarative1.succeed(
-        "deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm.*Remove a torrent'"
-    )
-    declarative2.succeed(
+    declarative.wait_for_unit("deluged")
+    declarative.wait_for_unit("delugeweb")
+    declarative.wait_until_succeeds("curl --fail http://declarative:3142")
+    declarative.succeed(
         "deluge-console 'connect 127.0.0.1:58846 andrew password; help' | grep -q 'rm.*Remove a torrent'"
     )
   '';
diff --git a/nixos/tests/dex-oidc.nix b/nixos/tests/dex-oidc.nix
new file mode 100644
index 0000000000000..37275a97ef0fb
--- /dev/null
+++ b/nixos/tests/dex-oidc.nix
@@ -0,0 +1,78 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "dex-oidc";
+  meta.maintainers = with lib.maintainers; [ Flakebi ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ jq ];
+    services.dex = {
+      enable = true;
+      settings = {
+        issuer = "http://127.0.0.1:8080/dex";
+        storage = {
+          type = "postgres";
+          config.host = "/var/run/postgresql";
+        };
+        web.http = "127.0.0.1:8080";
+        oauth2.skipApprovalScreen = true;
+        staticClients = [
+          {
+            id = "oidcclient";
+            name = "Client";
+            redirectURIs = [ "https://example.com/callback" ];
+            secretFile = "/etc/dex/oidcclient";
+          }
+        ];
+        connectors = [
+          {
+            type = "mockPassword";
+            id = "mock";
+            name = "Example";
+            config = {
+              username = "admin";
+              password = "password";
+            };
+          }
+        ];
+      };
+    };
+
+    # This should not be set from nix but through other means to not leak the secret.
+    environment.etc."dex/oidcclient" = {
+      mode = "0400";
+      user = "dex";
+      text = "oidcclientsecret";
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases =[ "dex" ];
+      ensureUsers = [
+        {
+          name = "dex";
+          ensurePermissions = { "DATABASE dex" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+  };
+
+  testScript = ''
+    with subtest("Web server gets ready"):
+        machine.wait_for_unit("dex.service")
+        # Wait until server accepts connections
+        machine.wait_until_succeeds("curl -fs 'localhost:8080/dex/auth/mock?client_id=oidcclient&response_type=code&redirect_uri=https://example.com/callback&scope=openid'")
+
+    with subtest("Login"):
+        state = machine.succeed("curl -fs 'localhost:8080/dex/auth/mock?client_id=oidcclient&response_type=code&redirect_uri=https://example.com/callback&scope=openid' | sed -n 's/.*state=\\(.*\\)\">.*/\\1/p'").strip()
+        print(f"Got state {state}")
+        machine.succeed(f"curl -fs 'localhost:8080/dex/auth/mock/login?back=&state={state}' -d 'login=admin&password=password'")
+        code = machine.succeed(f"curl -fs localhost:8080/dex/approval?req={state} | sed -n 's/.*code=\\(.*\\)&amp;.*/\\1/p'").strip()
+        print(f"Got approval code {code}")
+        bearer = machine.succeed(f"curl -fs localhost:8080/dex/token -u oidcclient:oidcclientsecret -d 'grant_type=authorization_code&redirect_uri=https://example.com/callback&code={code}' | jq .access_token -r").strip()
+        print(f"Got access token {bearer}")
+
+    with subtest("Get userinfo"):
+        assert '"sub"' in machine.succeed(
+            f"curl -fs localhost:8080/dex/userinfo --oauth2-bearer {bearer}"
+        )
+  '';
+})
diff --git a/nixos/tests/disable-installer-tools.nix b/nixos/tests/disable-installer-tools.nix
new file mode 100644
index 0000000000000..23c15faa8d334
--- /dev/null
+++ b/nixos/tests/disable-installer-tools.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
+
+{
+  name = "disable-installer-tools";
+
+  machine =
+    { pkgs, lib, ... }:
+    {
+        system.disableInstallerTools = true;
+        boot.enableContainers = false;
+        environment.defaultPackages = [];
+    };
+
+  testScript = ''
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+      with subtest("nixos installer tools should not be included"):
+          machine.fail("which nixos-rebuild")
+          machine.fail("which nixos-install")
+          machine.fail("which nixos-generate-config")
+          machine.fail("which nixos-enter")
+          machine.fail("which nixos-version")
+          machine.fail("which nixos-build-vms")
+
+      with subtest("perl should not be included"):
+          machine.fail("which perl")
+  '';
+})
diff --git a/nixos/tests/discourse.nix b/nixos/tests/discourse.nix
index 7dd39085a007a..cfac5f84a62fa 100644
--- a/nixos/tests/discourse.nix
+++ b/nixos/tests/discourse.nix
@@ -28,6 +28,8 @@ import ./make-test-python.nix (
       { nodes, ... }:
       {
         virtualisation.memorySize = 2048;
+        virtualisation.cores = 4;
+        virtualisation.useNixStoreImage = true;
 
         imports = [ common/user-account.nix ];
 
diff --git a/nixos/tests/doas.nix b/nixos/tests/doas.nix
index 5e9ce4b2c799c..7f038b2bee296 100644
--- a/nixos/tests/doas.nix
+++ b/nixos/tests/doas.nix
@@ -85,6 +85,14 @@ import ./make-test-python.nix (
       # ../../pkgs/tools/security/doas/0001-add-NixOS-specific-dirs-to-safe-PATH.patch
       with subtest("recursive calls to doas from subprocesses should succeed"):
           machine.succeed('doas -u test0 sh -c "doas -u test0 true"')
+
+      with subtest("test0 should inherit TERMINFO_DIRS from the user environment"):
+          dirs = machine.succeed(
+               "su - test0 -c 'doas -u root $SHELL -c \"echo \$TERMINFO_DIRS\"'"
+          )
+
+          if not "test0" in dirs:
+             raise Exception(f"user profile TERMINFO_DIRS is not preserved: {dirs}")
     '';
   }
 )
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 4c3c26980aa2d..7110187e8d764 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -119,7 +119,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     with subtest("The pullImage tool works"):
         docker.succeed(
-            "docker load --input='${examples.nixFromDockerHub}'",
+            "docker load --input='${examples.testNixFromDockerHub}'",
             "docker run --rm nix:2.2.1 nix-store --version",
             "docker rmi nix:2.2.1",
         )
@@ -378,5 +378,23 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         docker.succeed(
             "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/jane | grep -E ^1000$'"
         )
+
+    with subtest("exportImage produces a valid tarball"):
+        docker.succeed(
+            "tar -tf ${examples.exportBash} | grep '\./bin/bash' > /dev/null"
+        )
+
+    with subtest("Ensure bare paths in contents are loaded correctly"):
+        docker.succeed(
+            "docker load --input='${examples.build-image-with-path}'",
+            "docker run --rm build-image-with-path bash -c '[[ -e /hello.txt ]]'",
+            "docker rmi build-image-with-path",
+        )
+        docker.succeed(
+            "${examples.layered-image-with-path} | docker load",
+            "docker run --rm layered-image-with-path bash -c '[[ -e /hello.txt ]]'",
+            "docker rmi layered-image-with-path",
+        )
+
   '';
 })
diff --git a/nixos/tests/dokuwiki.nix b/nixos/tests/dokuwiki.nix
index 2664e1500ea44..67657e89f74c7 100644
--- a/nixos/tests/dokuwiki.nix
+++ b/nixos/tests/dokuwiki.nix
@@ -33,44 +33,79 @@ let
 in {
   name = "dokuwiki";
   meta = with pkgs.lib; {
-    maintainers = with maintainers; [ _1000101 ];
+    maintainers = with maintainers; [
+      _1000101
+      onny
+    ];
   };
-  machine = { ... }: {
-    services.dokuwiki."site1.local" = {
-      aclUse = false;
-      superUser = "admin";
+
+  nodes = {
+    dokuwiki_nginx = {...}: {
+      services.dokuwiki = {
+        sites = {
+          "site1.local" = {
+            aclUse = false;
+            superUser = "admin";
+          };
+          "site2.local" = {
+            usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
+            superUser = "admin";
+            templates = [ template-bootstrap3 ];
+            plugins = [ plugin-icalevents ];
+          };
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
-    services.dokuwiki."site2.local" = {
-      usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
-      superUser = "admin";
-      templates = [ template-bootstrap3 ];
-      plugins = [ plugin-icalevents ];
+
+    dokuwiki_caddy = {...}: {
+      services.dokuwiki = {
+        webserver = "caddy";
+        sites = {
+          "site1.local" = {
+            aclUse = false;
+            superUser = "admin";
+          };
+          "site2.local" = {
+            usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
+            superUser = "admin";
+            templates = [ template-bootstrap3 ];
+            plugins = [ plugin-icalevents ];
+          };
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
-    networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+
   };
 
   testScript = ''
-    site_names = ["site1.local", "site2.local"]
 
     start_all()
 
-    machine.wait_for_unit("phpfpm-dokuwiki-site1.local.service")
-    machine.wait_for_unit("phpfpm-dokuwiki-site2.local.service")
+    dokuwiki_nginx.wait_for_unit("nginx")
+    dokuwiki_caddy.wait_for_unit("caddy")
 
-    machine.wait_for_unit("nginx.service")
+    site_names = ["site1.local", "site2.local"]
 
-    machine.wait_for_open_port(80)
+    for machine in (dokuwiki_nginx, dokuwiki_caddy):
+      for site_name in site_names:
+        machine.wait_for_unit(f"phpfpm-dokuwiki-{site_name}")
 
-    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://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("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>'",
-    )
+        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/domination.nix b/nixos/tests/domination.nix
new file mode 100644
index 0000000000000..c76d4ed8c61b3
--- /dev/null
+++ b/nixos/tests/domination.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "domination";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.domination ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("domination >&2 &")
+      machine.wait_for_window("Menu")
+      machine.wait_for_text("New Game")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixos/tests/ec2.nix b/nixos/tests/ec2.nix
index df06724801684..aa3c2b7051f6c 100644
--- a/nixos/tests/ec2.nix
+++ b/nixos/tests/ec2.nix
@@ -24,6 +24,11 @@ let
           ln -s vda1 /dev/xvda1
         '';
 
+        # In a NixOS test the serial console is occupied by the "backdoor"
+        # (see testing/test-instrumentation.nix) and is incompatible with
+        # the configuration in virtualisation/amazon-image.nix.
+        systemd.services."serial-getty@ttyS0".enable = mkForce false;
+
         # Needed by nixos-rebuild due to the lack of network
         # access. Determined by trial and error.
         system.extraDependencies = with pkgs; ( [
diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix
index 2a1a4cba2956e..ae746d7e1f03d 100644
--- a/nixos/tests/elk.nix
+++ b/nixos/tests/elk.nix
@@ -1,12 +1,15 @@
+# To run the test on the unfree ELK use the folllowing command:
+# cd path/to/nixpkgs
+# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-6
+
 { system ? builtins.currentSystem,
   config ? {},
   pkgs ? import ../.. { inherit system config; },
-  enableUnfree ? false
-  # To run the test on the unfree ELK use the folllowing command:
-  # NIXPKGS_ALLOW_UNFREE=1 nix-build nixos/tests/elk.nix -A ELK-6 --arg enableUnfree true
 }:
 
 let
+  inherit (pkgs) lib;
+
   esUrl = "http://localhost:9200";
 
   mkElkTest = name : elk :
@@ -215,38 +218,40 @@ let
               '! curl --silent --show-error "${esUrl}/_cat/indices" | grep logstash | grep ^'
           )
     '';
-  }) {};
-in pkgs.lib.mapAttrs mkElkTest {
-  ELK-6 =
-    if enableUnfree
-    then {
+  }) { inherit pkgs system; };
+in {
+  ELK-6 = mkElkTest "elk-6-oss" {
+    name = "elk-6-oss";
+    elasticsearch = pkgs.elasticsearch6-oss;
+    logstash      = pkgs.logstash6-oss;
+    kibana        = pkgs.kibana6-oss;
+    journalbeat   = pkgs.journalbeat6;
+    metricbeat    = pkgs.metricbeat6;
+  };
+  # We currently only package upstream binaries.
+  # Feel free to package an SSPL licensed source-based package!
+  # ELK-7 = mkElkTest "elk-7-oss" {
+  #   name = "elk-7";
+  #   elasticsearch = pkgs.elasticsearch7-oss;
+  #   logstash      = pkgs.logstash7-oss;
+  #   kibana        = pkgs.kibana7-oss;
+  #   journalbeat   = pkgs.journalbeat7;
+  #   metricbeat    = pkgs.metricbeat7;
+  # };
+  unfree = lib.dontRecurseIntoAttrs {
+    ELK-6 = mkElkTest "elk-6" {
       elasticsearch = pkgs.elasticsearch6;
       logstash      = pkgs.logstash6;
       kibana        = pkgs.kibana6;
       journalbeat   = pkgs.journalbeat6;
       metricbeat    = pkgs.metricbeat6;
-    }
-    else {
-      elasticsearch = pkgs.elasticsearch6-oss;
-      logstash      = pkgs.logstash6-oss;
-      kibana        = pkgs.kibana6-oss;
-      journalbeat   = pkgs.journalbeat6;
-      metricbeat    = pkgs.metricbeat6;
     };
-  ELK-7 =
-    if enableUnfree
-    then {
+    ELK-7 = mkElkTest "elk-7" {
       elasticsearch = pkgs.elasticsearch7;
       logstash      = pkgs.logstash7;
       kibana        = pkgs.kibana7;
       journalbeat   = pkgs.journalbeat7;
       metricbeat    = pkgs.metricbeat7;
-    }
-    else {
-      elasticsearch = pkgs.elasticsearch7-oss;
-      logstash      = pkgs.logstash7-oss;
-      kibana        = pkgs.kibana7-oss;
-      journalbeat   = pkgs.journalbeat7;
-      metricbeat    = pkgs.metricbeat7;
     };
+  };
 }
diff --git a/nixos/tests/emacs-daemon.nix b/nixos/tests/emacs-daemon.nix
index 58bcd095990ad..e12da56021dab 100644
--- a/nixos/tests/emacs-daemon.nix
+++ b/nixos/tests/emacs-daemon.nix
@@ -33,7 +33,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       )
 
       # connects to the daemon
-      machine.succeed("emacsclient --create-frame $EDITOR &")
+      machine.succeed("emacsclient --create-frame $EDITOR >&2 &")
 
       # checks that Emacs shows the edited filename
       machine.wait_for_text("emacseditor")
diff --git a/nixos/tests/enlightenment.nix b/nixos/tests/enlightenment.nix
index cc1da649d493e..8506c348246de 100644
--- a/nixos/tests/enlightenment.nix
+++ b/nixos/tests/enlightenment.nix
@@ -11,15 +11,14 @@ import ./make-test-python.nix ({ pkgs, ...} :
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
     services.xserver.desktopManager.enlightenment.enable = true;
-    services.xserver.displayManager.lightdm = {
-      enable = true;
+    services.xserver.displayManager = {
+      lightdm.enable = true;
       autoLogin = {
         enable = true;
         user = "alice";
       };
     };
     hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
-    virtualisation.memorySize = 1024;
     environment.systemPackages = [ pkgs.xdotool ];
     services.acpid.enable = true;
     services.connman.enable = true;
@@ -88,7 +87,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.screenshot("wizard12")
 
     with subtest("Run Terminology"):
-        machine.succeed("terminology &")
+        machine.succeed("terminology >&2 &")
         machine.sleep(5)
         machine.send_chars("ls --color -alF\n")
         machine.sleep(2)
diff --git a/nixos/tests/etesync-dav.nix b/nixos/tests/etesync-dav.nix
index da5c056f53497..6a747e23f76f6 100644
--- a/nixos/tests/etesync-dav.nix
+++ b/nixos/tests/etesync-dav.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ''
       machine.wait_for_unit("multi-user.target")
       machine.succeed("etesync-dav --version")
-      machine.execute("etesync-dav &")
+      machine.execute("etesync-dav >&2 &")
       machine.wait_for_open_port(37358)
       with subtest("Check that the web interface is accessible"):
           assert "Add User" in machine.succeed("curl -s http://localhost:37358/.web/add/")
diff --git a/nixos/tests/fcitx/default.nix b/nixos/tests/fcitx/default.nix
index cbeb95d33b0cb..a243be8dc19b1 100644
--- a/nixos/tests/fcitx/default.nix
+++ b/nixos/tests/fcitx/default.nix
@@ -11,7 +11,6 @@ import ../make-test-python.nix (
           ...
         }:
           {
-            virtualisation.memorySize = 1024;
 
             imports = [
               ../common/user-account.nix
diff --git a/nixos/tests/fenics.nix b/nixos/tests/fenics.nix
index 56f09d6a27e40..f0a8c32c7cd8e 100644
--- a/nixos/tests/fenics.nix
+++ b/nixos/tests/fenics.nix
@@ -38,7 +38,6 @@ in
         gcc
         (python3.withPackages (ps: with ps; [ fenics ]))
       ];
-      virtualisation.memorySize = 512;
     };
   };
   testScript =
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index 4ad45c0224072..6101fc9735641 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -13,9 +13,6 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
         pkgs.xdotool
       ];
 
-      # Need some more memory to record audio.
-      virtualisation.memorySize = "500";
-
       # Create a virtual sound device, with mixing
       # and all, for recording audio.
       boot.kernelModules = [ "snd-aloop" ];
@@ -91,7 +88,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
 
       with subtest("Wait until Firefox has finished loading the Valgrind docs page"):
           machine.execute(
-              "xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' &"
+              "xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' >&2 &"
           )
           machine.wait_for_window("Valgrind")
           machine.sleep(40)
@@ -99,7 +96,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
       with subtest("Check whether Firefox can play sound"):
           with audio_recording(machine):
               machine.succeed(
-                  "firefox file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga &"
+                  "firefox file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga >&2 &"
               )
               wait_for_sound(machine)
           machine.copy_from_vm("/tmp/record.wav")
diff --git a/nixos/tests/ft2-clone.nix b/nixos/tests/ft2-clone.nix
index c877054234ec8..71eda43e2b245 100644
--- a/nixos/tests/ft2-clone.nix
+++ b/nixos/tests/ft2-clone.nix
@@ -22,7 +22,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       # Add a dummy sound card, or the program won't start
       machine.execute("modprobe snd-dummy")
 
-      machine.execute("ft2-clone &")
+      machine.execute("ft2-clone >&2 &")
 
       machine.wait_for_window(r"Fasttracker")
       machine.sleep(5)
diff --git a/nixos/tests/gerrit.nix b/nixos/tests/gerrit.nix
index b6b6486fae86c..8ae9e89cf6b0c 100644
--- a/nixos/tests/gerrit.nix
+++ b/nixos/tests/gerrit.nix
@@ -18,7 +18,6 @@ in {
       { config, pkgs, ... }: {
         networking.firewall.allowedTCPPorts = [ 80 2222 ];
 
-        virtualisation.memorySize = 1024;
 
         services.gerrit = {
           enable = true;
diff --git a/nixos/tests/ghostunnel.nix b/nixos/tests/ghostunnel.nix
index a82cff8082b75..8bea648540216 100644
--- a/nixos/tests/ghostunnel.nix
+++ b/nixos/tests/ghostunnel.nix
@@ -1,5 +1,4 @@
-{ pkgs, ... }: import ./make-test-python.nix {
-
+import ./make-test-python.nix ({ pkgs, ... }: {
   nodes = {
     backend = { pkgs, ... }: {
       services.nginx.enable = true;
@@ -101,4 +100,4 @@
   meta.maintainers = with pkgs.lib.maintainers; [
     roberth
   ];
-}
+})
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index 3e9feeb0769da..dc3b889c8e8e2 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -14,6 +14,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
       imports = [ common/user-account.nix ];
 
       virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
+      virtualisation.cores = 4;
+      virtualisation.useNixStoreImage = true;
       systemd.services.gitlab.serviceConfig.Restart = mkForce "no";
       systemd.services.gitlab-workhorse.serviceConfig.Restart = mkForce "no";
       systemd.services.gitaly.serviceConfig.Restart = mkForce "no";
diff --git a/nixos/tests/gnome-xorg.nix b/nixos/tests/gnome-xorg.nix
index 55f9c90c20a08..6264b87af4ec5 100644
--- a/nixos/tests/gnome-xorg.nix
+++ b/nixos/tests/gnome-xorg.nix
@@ -25,7 +25,21 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       services.xserver.desktopManager.gnome.debug = true;
       services.xserver.displayManager.defaultSession = "gnome-xorg";
 
-      virtualisation.memorySize = 1024;
+      systemd.user.services = {
+        "org.gnome.Shell@x11" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
     };
 
   testScript = { nodes, ... }: let
diff --git a/nixos/tests/gnome.nix b/nixos/tests/gnome.nix
index e8d18a41bd064..06f387ecad67d 100644
--- a/nixos/tests/gnome.nix
+++ b/nixos/tests/gnome.nix
@@ -30,7 +30,21 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
         })
       ];
 
-      virtualisation.memorySize = 1024;
+      systemd.user.services = {
+        "org.gnome.Shell@wayland" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
     };
 
   testScript = { nodes, ... }: let
diff --git a/nixos/tests/graphite.nix b/nixos/tests/graphite.nix
index 137be2d89c8b3..496f16846ea6a 100644
--- a/nixos/tests/graphite.nix
+++ b/nixos/tests/graphite.nix
@@ -4,7 +4,6 @@ import ./make-test-python.nix ({ pkgs, ... } :
   nodes = {
     one =
       { ... }: {
-        virtualisation.memorySize = 1024;
         time.timeZone = "UTC";
         services.graphite = {
           web = {
diff --git a/nixos/tests/hadoop/hadoop.nix b/nixos/tests/hadoop/hadoop.nix
new file mode 100644
index 0000000000000..48737debab546
--- /dev/null
+++ b/nixos/tests/hadoop/hadoop.nix
@@ -0,0 +1,228 @@
+# This test is very comprehensive. It tests whether all hadoop services work well with each other.
+# Run this when updating the Hadoop package or making significant changes to the hadoop module.
+# For a more basic test, see hdfs.nix and yarn.nix
+import ../make-test-python.nix ({pkgs, ...}: {
+
+  nodes = let
+    package = pkgs.hadoop;
+    coreSite = {
+      "fs.defaultFS" = "hdfs://ns1";
+    };
+    hdfsSite = {
+      "dfs.namenode.rpc-bind-host" = "0.0.0.0";
+      "dfs.namenode.http-bind-host" = "0.0.0.0";
+      "dfs.namenode.servicerpc-bind-host" = "0.0.0.0";
+
+      # HA Quorum Journal Manager configuration
+      "dfs.nameservices" = "ns1";
+      "dfs.ha.namenodes.ns1" = "nn1,nn2";
+      "dfs.namenode.shared.edits.dir.ns1.nn1" = "qjournal://jn1:8485;jn2:8485;jn3:8485/ns1";
+      "dfs.namenode.shared.edits.dir.ns1.nn2" = "qjournal://jn1:8485;jn2:8485;jn3:8485/ns1";
+      "dfs.namenode.rpc-address.ns1.nn1" = "nn1:8020";
+      "dfs.namenode.rpc-address.ns1.nn2" = "nn2:8020";
+      "dfs.namenode.servicerpc-address.ns1.nn1" = "nn1:8022";
+      "dfs.namenode.servicerpc-address.ns1.nn2" = "nn2:8022";
+      "dfs.namenode.http-address.ns1.nn1" = "nn1:9870";
+      "dfs.namenode.http-address.ns1.nn2" = "nn2:9870";
+
+      # Automatic failover configuration
+      "dfs.client.failover.proxy.provider.ns1" = "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider";
+      "dfs.ha.automatic-failover.enabled.ns1" = "true";
+      "dfs.ha.fencing.methods" = "shell(true)";
+      "ha.zookeeper.quorum" = "zk1:2181";
+    };
+    yarnSiteHA = {
+      "yarn.resourcemanager.zk-address" = "zk1:2181";
+      "yarn.resourcemanager.ha.enabled" = "true";
+      "yarn.resourcemanager.ha.rm-ids" = "rm1,rm2";
+      "yarn.resourcemanager.hostname.rm1" = "rm1";
+      "yarn.resourcemanager.hostname.rm2" = "rm2";
+      "yarn.resourcemanager.ha.automatic-failover.enabled" = "true";
+      "yarn.resourcemanager.cluster-id" = "cluster1";
+      # yarn.resourcemanager.webapp.address needs to be defined even though yarn.resourcemanager.hostname is set. This shouldn't be necessary, but there's a bug in
+      # hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/amfilter/AmFilterInitializer.java:70
+      # that causes AM containers to fail otherwise.
+      "yarn.resourcemanager.webapp.address.rm1" = "rm1:8088";
+      "yarn.resourcemanager.webapp.address.rm2" = "rm2:8088";
+    };
+  in {
+    zk1 = { ... }: {
+      services.zookeeper.enable = true;
+      networking.firewall.allowedTCPPorts = [ 2181 ];
+    };
+
+    # HDFS cluster
+    nn1 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        hdfs.namenode.enable = true;
+        hdfs.zkfc.enable = true;
+      };
+    };
+    nn2 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        hdfs.namenode.enable = true;
+        hdfs.zkfc.enable = true;
+      };
+    };
+
+    jn1 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        hdfs.journalnode.enable = true;
+      };
+    };
+    jn2 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        hdfs.journalnode.enable = true;
+      };
+    };
+    jn3 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        hdfs.journalnode.enable = true;
+      };
+    };
+
+    dn1 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        hdfs.datanode.enable = true;
+      };
+    };
+
+    # YARN cluster
+    rm1 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        yarnSite = options.services.hadoop.yarnSite.default // yarnSiteHA;
+        yarn.resourcemanager.enable = true;
+      };
+    };
+    rm2 = {pkgs, options, ...}: {
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        yarnSite = options.services.hadoop.yarnSite.default // yarnSiteHA;
+        yarn.resourcemanager.enable = true;
+      };
+    };
+    nm1 = {pkgs, options, ...}: {
+      virtualisation.memorySize = 2048;
+      services.hadoop = {
+        inherit package coreSite hdfsSite;
+        yarnSite = options.services.hadoop.yarnSite.default // yarnSiteHA;
+        yarn.nodemanager.enable = true;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    #### HDFS tests ####
+
+    zk1.wait_for_unit("network.target")
+    jn1.wait_for_unit("network.target")
+    jn2.wait_for_unit("network.target")
+    jn3.wait_for_unit("network.target")
+    nn1.wait_for_unit("network.target")
+    nn2.wait_for_unit("network.target")
+    dn1.wait_for_unit("network.target")
+
+    zk1.wait_for_unit("zookeeper")
+    jn1.wait_for_unit("hdfs-journalnode")
+    jn2.wait_for_unit("hdfs-journalnode")
+    jn3.wait_for_unit("hdfs-journalnode")
+
+    zk1.wait_for_open_port(2181)
+    jn1.wait_for_open_port(8480)
+    jn1.wait_for_open_port(8485)
+    jn2.wait_for_open_port(8480)
+    jn2.wait_for_open_port(8485)
+
+    # Namenodes must be stopped before initializing the cluster
+    nn1.succeed("systemctl stop hdfs-namenode")
+    nn2.succeed("systemctl stop hdfs-namenode")
+    nn1.succeed("systemctl stop hdfs-zkfc")
+    nn2.succeed("systemctl stop hdfs-zkfc")
+
+    # Initialize zookeeper for failover controller
+    nn1.succeed("sudo -u hdfs hdfs zkfc -formatZK 2>&1 | systemd-cat")
+
+    # Format NN1 and start it
+    nn1.succeed("sudo -u hdfs hadoop namenode -format 2>&1 | systemd-cat")
+    nn1.succeed("systemctl start hdfs-namenode")
+    nn1.wait_for_open_port(9870)
+    nn1.wait_for_open_port(8022)
+    nn1.wait_for_open_port(8020)
+
+    # Bootstrap NN2 from NN1 and start it
+    nn2.succeed("sudo -u hdfs hdfs namenode -bootstrapStandby 2>&1 | systemd-cat")
+    nn2.succeed("systemctl start hdfs-namenode")
+    nn2.wait_for_open_port(9870)
+    nn2.wait_for_open_port(8022)
+    nn2.wait_for_open_port(8020)
+    nn1.succeed("netstat -tulpne | systemd-cat")
+
+    # Start failover controllers
+    nn1.succeed("systemctl start hdfs-zkfc")
+    nn2.succeed("systemctl start hdfs-zkfc")
+
+    # DN should have started by now, but confirm anyway
+    dn1.wait_for_unit("hdfs-datanode")
+    # Print states of namenodes
+    dn1.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    # Wait for cluster to exit safemode
+    dn1.succeed("sudo -u hdfs hdfs dfsadmin -safemode wait")
+    dn1.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    # test R/W
+    dn1.succeed("echo testfilecontents | sudo -u hdfs hdfs dfs -put - /testfile")
+    assert "testfilecontents" in dn1.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
+
+    # Test NN failover
+    nn1.succeed("systemctl stop hdfs-namenode")
+    assert "active" in dn1.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState")
+    dn1.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    assert "testfilecontents" in dn1.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
+
+    nn1.succeed("systemctl start hdfs-namenode")
+    nn1.wait_for_open_port(9870)
+    nn1.wait_for_open_port(8022)
+    nn1.wait_for_open_port(8020)
+    assert "standby" in dn1.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState")
+    dn1.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+
+    #### YARN tests ####
+
+    rm1.wait_for_unit("network.target")
+    rm2.wait_for_unit("network.target")
+    nm1.wait_for_unit("network.target")
+
+    rm1.wait_for_unit("yarn-resourcemanager")
+    rm1.wait_for_open_port(8088)
+    rm2.wait_for_unit("yarn-resourcemanager")
+    rm2.wait_for_open_port(8088)
+
+    nm1.wait_for_unit("yarn-nodemanager")
+    nm1.wait_for_open_port(8042)
+    nm1.wait_for_open_port(8040)
+    nm1.wait_until_succeeds("yarn node -list | grep Nodes:1")
+    nm1.succeed("sudo -u yarn yarn rmadmin -getAllServiceState | systemd-cat")
+    nm1.succeed("sudo -u yarn yarn node -list | systemd-cat")
+
+    # Test RM failover
+    rm1.succeed("systemctl stop yarn-resourcemanager")
+    assert "standby" not in nm1.succeed("sudo -u yarn yarn rmadmin -getAllServiceState")
+    nm1.succeed("sudo -u yarn yarn rmadmin -getAllServiceState | systemd-cat")
+    rm1.succeed("systemctl start yarn-resourcemanager")
+    rm1.wait_for_unit("yarn-resourcemanager")
+    rm1.wait_for_open_port(8088)
+    assert "standby" in nm1.succeed("sudo -u yarn yarn rmadmin -getAllServiceState")
+    nm1.succeed("sudo -u yarn yarn rmadmin -getAllServiceState | systemd-cat")
+
+    assert "Estimated value of Pi is" in nm1.succeed("HADOOP_USER_NAME=hdfs yarn jar $(readlink $(which yarn) | sed -r 's~bin/yarn~lib/hadoop-*/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar~g') pi 2 10")
+    assert "SUCCEEDED" in nm1.succeed("yarn application -list -appStates FINISHED")
+  '';
+})
diff --git a/nixos/tests/hadoop/hdfs.nix b/nixos/tests/hadoop/hdfs.nix
index f1f98ed42eb31..b63cbf4803271 100644
--- a/nixos/tests/hadoop/hdfs.nix
+++ b/nixos/tests/hadoop/hdfs.nix
@@ -1,36 +1,33 @@
+# Test a minimal HDFS cluster with no HA
 import ../make-test-python.nix ({...}: {
   nodes = {
     namenode = {pkgs, ...}: {
       services.hadoop = {
-        package = pkgs.hadoop_3_1;
-        hdfs.namenode.enabled = true;
+        package = pkgs.hadoop;
+        hdfs = {
+          namenode = {
+            enable = true;
+            formatOnInit = true;
+          };
+          httpfs.enable = true;
+        };
         coreSite = {
           "fs.defaultFS" = "hdfs://namenode:8020";
-        };
-        hdfsSite = {
-          "dfs.replication" = 1;
-          "dfs.namenode.rpc-bind-host" = "0.0.0.0";
-          "dfs.namenode.http-bind-host" = "0.0.0.0";
+          "hadoop.proxyuser.httpfs.groups" = "*";
+          "hadoop.proxyuser.httpfs.hosts" = "*";
         };
       };
-      networking.firewall.allowedTCPPorts = [
-        9870 # namenode.http-address
-        8020 # namenode.rpc-address
-      ];
     };
     datanode = {pkgs, ...}: {
       services.hadoop = {
-        package = pkgs.hadoop_3_1;
-        hdfs.datanode.enabled = true;
+        package = pkgs.hadoop;
+        hdfs.datanode.enable = true;
         coreSite = {
           "fs.defaultFS" = "hdfs://namenode:8020";
+          "hadoop.proxyuser.httpfs.groups" = "*";
+          "hadoop.proxyuser.httpfs.hosts" = "*";
         };
       };
-      networking.firewall.allowedTCPPorts = [
-        9864 # datanode.http.address
-        9866 # datanode.address
-        9867 # datanode.ipc.address
-      ];
     };
   };
 
@@ -50,5 +47,13 @@ import ../make-test-python.nix ({...}: {
 
     namenode.succeed("curl -f http://namenode:9870")
     datanode.succeed("curl -f http://datanode:9864")
+
+    datanode.succeed("sudo -u hdfs hdfs dfsadmin -safemode wait")
+    datanode.succeed("echo testfilecontents | sudo -u hdfs hdfs dfs -put - /testfile")
+    assert "testfilecontents" in datanode.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
+
+    namenode.wait_for_unit("hdfs-httpfs")
+    namenode.wait_for_open_port(14000)
+    assert "testfilecontents" in datanode.succeed("curl -f \"http://namenode:14000/webhdfs/v1/testfile?user.name=hdfs&op=OPEN\" 2>&1")
   '';
 })
diff --git a/nixos/tests/hadoop/yarn.nix b/nixos/tests/hadoop/yarn.nix
index 01077245d3973..09bdb35791c7e 100644
--- a/nixos/tests/hadoop/yarn.nix
+++ b/nixos/tests/hadoop/yarn.nix
@@ -1,28 +1,20 @@
+# This only tests if YARN is able to start its services
 import ../make-test-python.nix ({...}: {
   nodes = {
     resourcemanager = {pkgs, ...}: {
-      services.hadoop.package = pkgs.hadoop_3_1;
-      services.hadoop.yarn.resourcemanager.enabled = true;
+      services.hadoop.package = pkgs.hadoop;
+      services.hadoop.yarn.resourcemanager.enable = true;
       services.hadoop.yarnSite = {
         "yarn.resourcemanager.scheduler.class" = "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler";
       };
-      networking.firewall.allowedTCPPorts = [
-        8088 # resourcemanager.webapp.address
-        8031 # resourcemanager.resource-tracker.address
-      ];
     };
     nodemanager = {pkgs, ...}: {
-      services.hadoop.package = pkgs.hadoop_3_1;
-      services.hadoop.yarn.nodemanager.enabled = true;
+      services.hadoop.package = pkgs.hadoop;
+      services.hadoop.yarn.nodemanager.enable = true;
       services.hadoop.yarnSite = {
         "yarn.resourcemanager.hostname" = "resourcemanager";
         "yarn.nodemanager.log-dirs" = "/tmp/userlogs";
-        "yarn.nodemanager.address" = "0.0.0.0:8041";
       };
-      networking.firewall.allowedTCPPorts = [
-        8042 # nodemanager.webapp.address
-        8041 # nodemanager.address
-      ];
     };
 
   };
@@ -38,7 +30,6 @@ import ../make-test-python.nix ({...}: {
     nodemanager.wait_for_unit("yarn-nodemanager")
     nodemanager.wait_for_unit("network.target")
     nodemanager.wait_for_open_port(8042)
-    nodemanager.wait_for_open_port(8041)
 
     resourcemanager.succeed("curl -f http://localhost:8088")
     nodemanager.succeed("curl -f http://localhost:8042")
diff --git a/nixos/tests/handbrake.nix b/nixos/tests/handbrake.nix
index 226dc8b2aa8a8..d2d41b372be19 100644
--- a/nixos/tests/handbrake.nix
+++ b/nixos/tests/handbrake.nix
@@ -1,15 +1,19 @@
 import ./make-test-python.nix ({ pkgs, ... }:
+
 let
   # Download Big Buck Bunny example, licensed under CC Attribution 3.0.
   testMkv = pkgs.fetchurl {
     url = "https://github.com/Matroska-Org/matroska-test-files/blob/cf0792be144ac470c4b8052cfe19bb691993e3a2/test_files/test1.mkv?raw=true";
     sha256 = "1hfxbbgxwfkzv85pvpvx55a72qsd0hxjbm9hkl5r3590zw4s75h9";
+    name = "test1.mkv";
   };
-in {
+
+in
+{
   name = "handbrake";
 
   meta = {
-    maintainers = with pkgs.lib.maintainers; [ danieldk ];
+    maintainers = with pkgs.lib.maintainers; [ ];
   };
 
   machine = { pkgs, ... }: {
@@ -21,11 +25,9 @@ in {
     # only takes a few seconds.
     start_all()
 
-    machine.succeed(
-        "HandBrakeCLI -i ${testMkv} -o test.mp4 -e x264 -q 20 -B 160"
-    )
-    machine.succeed(
-        "HandBrakeCLI -i ${testMkv} -o test.mkv -e x264 -q 20 -B 160"
-    )
+    machine.succeed("HandBrakeCLI -i ${testMkv} -o test.mp4 -e x264 -q 20 -B 160")
+    machine.succeed("test -e test.mp4")
+    machine.succeed("HandBrakeCLI -i ${testMkv} -o test.mkv -e x264 -q 20 -B 160")
+    machine.succeed("test -e test.mkv")
   '';
 })
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index a0b629086b5ad..da7e0972e131a 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... } : {
+import ./make-test-python.nix ({ pkgs, ... } : {
   name = "hardened";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ joachifm ];
@@ -10,8 +10,6 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... } : {
     { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
       imports = [ ../modules/profiles/hardened.nix ];
-      boot.kernelPackages =
-        lib.mkIf latestKernel pkgs.linuxPackages_latest_hardened;
       environment.memoryAllocator.provider = "graphene-hardened";
       nix.useSandbox = false;
       virtualisation.emptyDiskImages = [ 4096 ];
@@ -57,6 +55,7 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... } : {
       # Test kernel module hardening
       with subtest("No more kernel modules can be loaded"):
           # note: this better a be module we normally wouldn't load ...
+          machine.wait_for_unit("disable-kernel-module-loading.service")
           machine.fail("modprobe dccp")
 
 
diff --git a/nixos/tests/herbstluftwm.nix b/nixos/tests/herbstluftwm.nix
index 2c98cceee6a21..7d079f4bfb695 100644
--- a/nixos/tests/herbstluftwm.nix
+++ b/nixos/tests/herbstluftwm.nix
@@ -3,7 +3,6 @@ import ./make-test-python.nix ({ lib, ...} : {
 
   meta = {
     maintainers = with lib.maintainers; [ thibautmarty ];
-    timeout = 30;
   };
 
   machine = { pkgs, lib, ... }: {
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
index ae506c8542fe0..fc6857e2fd024 100644
--- a/nixos/tests/hibernate.nix
+++ b/nixos/tests/hibernate.nix
@@ -51,6 +51,7 @@ in makeTest {
         connect-timeout = 1
       '';
 
+      virtualisation.memorySize = 2048;
       virtualisation.diskSize = 8 * 1024;
       virtualisation.emptyDiskImages = [
         # Small root disk for installer
@@ -68,7 +69,7 @@ in makeTest {
   testScript =
     ''
       def create_named_machine(name):
-          return create_machine(
+          machine = create_machine(
               {
                   "qemuFlags": "-cpu max ${
                     if system == "x86_64-linux" then "-m 1024"
@@ -78,6 +79,8 @@ in makeTest {
                   "name": name,
               }
           )
+          driver.machines.append(machine)
+          return machine
 
 
       # Install NixOS
@@ -93,7 +96,7 @@ in makeTest {
           "mkswap /dev/vda1 -L swap",
           # Install onto /mnt
           "nix-store --load-db < ${pkgs.closureInfo {rootPaths = [installedSystem];}}/registration",
-          "nixos-install --root /mnt --system ${installedSystem} --no-root-passwd",
+          "nixos-install --root /mnt --system ${installedSystem} --no-root-passwd --no-channel-copy >&2",
       )
       machine.shutdown()
 
@@ -108,7 +111,7 @@ in makeTest {
       )
 
       # Hibernate machine
-      hibernate.succeed("systemctl hibernate &")
+      hibernate.execute("systemctl hibernate >&2 &", check_return=False)
       hibernate.wait_for_shutdown()
 
       # Restore machine from hibernation, validate our ramfs file is there.
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 699be8fd7dc6a..0894736bac9c3 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -12,13 +12,14 @@ in {
     environment.systemPackages = with pkgs; [ mosquitto ];
     services.mosquitto = {
       enable = true;
-      checkPasswords = true;
-      users = {
-        "${mqttUsername}" = {
-          acl = [ "topic readwrite #" ];
-          password = mqttPassword;
+      listeners = [ {
+        users = {
+          "${mqttUsername}" = {
+            acl = [ "readwrite #" ];
+            password = mqttPassword;
+          };
         };
-      };
+      } ];
     };
     services.home-assistant = {
       inherit configDir;
diff --git a/nixos/tests/ihatemoney.nix b/nixos/tests/ihatemoney/default.nix
index 0451a4505808b..78278d2e86996 100644
--- a/nixos/tests/ihatemoney.nix
+++ b/nixos/tests/ihatemoney/default.nix
@@ -1,22 +1,36 @@
 { system ? builtins.currentSystem,
   config ? {},
-  pkgs ? import ../.. { inherit system config; }
+  pkgs ? import ../../.. { inherit system config; }
 }:
 
 let
-  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (import ../../lib/testing-python.nix { inherit system pkgs; }) makeTest;
   f = backend: makeTest {
     name = "ihatemoney-${backend}";
-    machine = { lib, ... }: {
+    machine = { nodes, lib, ... }: {
       services.ihatemoney = {
         enable = true;
         enablePublicProjectCreation = true;
+        secureCookie = false;
         inherit backend;
         uwsgiConfig = {
           http = ":8000";
         };
       };
       boot.cleanTmpDir = true;
+      # for exchange rates
+      security.pki.certificateFiles = [ ./server.crt ];
+      networking.extraHosts = "127.0.0.1 api.exchangerate.host";
+      services.nginx = {
+        enable = true;
+        virtualHosts."api.exchangerate.host" = {
+          addSSL = true;
+          # openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 1000000 -nodes -subj '/CN=api.exchangerate.host'
+          sslCertificate = ./server.crt;
+          sslCertificateKey = ./server.key;
+          locations."/".return = "200 '${builtins.readFile ./rates.json}'";
+        };
+      };
       # ihatemoney needs a local smtp server otherwise project creation just crashes
       services.opensmtpd = {
         enable = true;
@@ -30,11 +44,13 @@ let
     testScript = ''
       machine.wait_for_open_port(8000)
       machine.wait_for_unit("uwsgi.service")
-      machine.wait_until_succeeds("curl http://localhost:8000")
+      machine.wait_until_succeeds("curl --fail https://api.exchangerate.host")
+      machine.wait_until_succeeds("curl --fail 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'"
+      result = machine.succeed(
+          "curl --fail -X POST http://localhost:8000/api/projects -d 'name=yay&id=yay&password=yay&contact_email=yay@example.com&default_currency=XXX'"
       )
+      assert '"yay"' in result, repr(result)
       owner, timestamp = machine.succeed(
           "stat --printf %U:%G___%Y /var/lib/ihatemoney/secret_key"
       ).split("___")
@@ -47,13 +63,13 @@ let
           machine.wait_for_unit("uwsgi.service")
 
       with subtest("check that the database is really persistent"):
-          machine.succeed("curl --basic -u yay:yay http://localhost:8000/api/projects/yay")
+          machine.succeed("curl --fail --basic -u yay:yay http://localhost:8000/api/projects/yay")
 
       with subtest("check that the secret key is really persistent"):
           timestamp2 = machine.succeed("stat --printf %Y /var/lib/ihatemoney/secret_key")
           assert timestamp == timestamp2
 
-      assert "ihatemoney" in machine.succeed("curl http://localhost:8000")
+      assert "ihatemoney" in machine.succeed("curl --fail http://localhost:8000")
     '';
   };
 in {
diff --git a/nixos/tests/ihatemoney/rates.json b/nixos/tests/ihatemoney/rates.json
new file mode 100644
index 0000000000000..ebdd2651b0401
--- /dev/null
+++ b/nixos/tests/ihatemoney/rates.json
@@ -0,0 +1,39 @@
+{
+  "rates": {
+    "CAD": 1.3420055134,
+    "HKD": 7.7513783598,
+    "ISK": 135.9407305307,
+    "PHP": 49.3762922123,
+    "DKK": 6.4126464507,
+    "HUF": 298.9145416954,
+    "CZK": 22.6292212267,
+    "GBP": 0.7838128877,
+    "RON": 4.1630771881,
+    "SEK": 8.8464851826,
+    "IDR": 14629.5658166782,
+    "INR": 74.8328738801,
+    "BRL": 5.2357856651,
+    "RUB": 71.8416609235,
+    "HRK": 6.4757064094,
+    "JPY": 106.2715368711,
+    "THB": 31.7203652653,
+    "CHF": 0.9243625086,
+    "EUR": 0.8614748449,
+    "MYR": 4.2644727774,
+    "BGN": 1.6848725017,
+    "TRY": 6.8483804273,
+    "CNY": 7.0169710544,
+    "NOK": 9.213731909,
+    "NZD": 1.5080978635,
+    "ZAR": 16.7427636113,
+    "USD": 1,
+    "MXN": 22.4676085458,
+    "SGD": 1.3855099931,
+    "AUD": 1.4107512061,
+    "ILS": 3.4150585803,
+    "KRW": 1203.3339076499,
+    "PLN": 3.794452102
+  },
+  "base": "USD",
+  "date": "2020-07-24"
+}
diff --git a/nixos/tests/ihatemoney/server.crt b/nixos/tests/ihatemoney/server.crt
new file mode 100644
index 0000000000000..10e568b14b147
--- /dev/null
+++ b/nixos/tests/ihatemoney/server.crt
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEvjCCAqYCCQDkTQrENPCZjjANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVh
+cGkuZXhjaGFuZ2VyYXRlLmhvc3QwIBcNMjEwNzE0MTI1MzQ0WhgPNDc1OTA2MTEx
+MjUzNDRaMCAxHjAcBgNVBAMMFWFwaS5leGNoYW5nZXJhdGUuaG9zdDCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5zpwUYa/ySqvJ/PUnXYsl1ww5SNGJh
+NujCRxC0Gw+5t5O7USSHRdz7Eb2PNFMa7JR+lliLAWdjHfqPXJWmP10X5ebvyxeQ
+TJkR1HpDSY6TQQlJvwr/JNGryyoQYjXvnyeyVu4TS3U0TTI631OonDAj+HbFIs9L
+gr/HfHzFmxRVLwaJ7hebanihc5RzoWTxgswiOwYQu5AivXQqcvUIxELeT7CxWwiw
+be/SlalDgoezB/poqaa215FUuN2av+nTn+swH3WOi9kwePLgVKn9BnDMwyh8et13
+yt27RWCSOcZagRSYsSbBaEJbClZvnuYvDqooJEy0GVbGBZpClKRKe92yd0PTf3ZJ
+GupyNoCFQlGugY//WLrsPv/Q4WwP+qZ6t97sV0CdM+epKVde/LfPKn+tFMv86qIg
+Q/uGHdDwUI8XH2EysAavhdlssSrovmpl4hyo9UkzTWfJgAbmOZY3Vba41wsq12FT
+usDsswGLBD10MdXWltR/Hdk8OnosLmeJxfZODAv31KSfd+4b6Ntr9BYQvAQSO+1/
+Mf7gEQtNhO003VKIyV5cpH4kVQieEcvoEKgq32NVBSKVf6UIPWIefu19kvrttaUu
+Q2QW2Qm4Ph/4cWpxl0jcrN5rjmgaBtIMmKYjRIS0ThDWzfVkJdmJuATzExJAplLN
+nYPBG3gOtQQpAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAJzt/aN7wl88WrvBasVi
+fSJmJjRaW2rYyBUMptQNkm9ElHN2eQQxJgLi8+9ArQxuGKhHx+D1wMGF8w2yOp0j
+4atfbXDcT+cTQY55qdEeYgU8KhESHHGszGsUpv7hzU2cACZiXG0YbOmORFYcn49Z
+yPyN98kW8BViLzNF9v+I/NJPuaaCeWKjXCqY2GCzddiuotrlLtz0CODXZJ506I1F
+38vQgZb10yAe6+R4y0BK7sUlmfr9BBqVcDQ/z74Kph1aB32zwP8KrNitwG1Tyk6W
+rxD1dStEQyX8uDPAspe2JrToMWsOMje9F5lotmuzyvwRJYfAav300EtIggBqpiHR
+o0P/1xxBzmaCHxEUJegdoYg8Q27llqsjR2T78uv/BlxpX9Dv5kNex5EZThKqyz4a
+Fn1VqiA3D9IsvxH4ud+8eDaP24u1yYObSTDIBsw9xDvoV8fV+NWoNNhcAL5GwC0P
+Goh7/brZSHUprxGpwRB524E//8XmCsRd/+ShtXbi4gEODMH4xLdkD7fZIJC4eG1H
+GOVc1MwjiYvbQlPs6MOcQ0iKQneSlaEJmyyO5Ro5OKiKj89Az/mLYX3R17AIsu0T
+Q5pGcmhKVRyu0zXvkGfK352TLwoe+4vbmakDq21Pkkcy8V9M4wP+vpCfQkg1REQ1
++mr1Vg+SFya3mlCxpFTy3j8E
+-----END CERTIFICATE-----
diff --git a/nixos/tests/ihatemoney/server.key b/nixos/tests/ihatemoney/server.key
new file mode 100644
index 0000000000000..72a43577d64da
--- /dev/null
+++ b/nixos/tests/ihatemoney/server.key
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC+c6cFGGv8kqry
+fz1J12LJdcMOUjRiYTbowkcQtBsPubeTu1Ekh0Xc+xG9jzRTGuyUfpZYiwFnYx36
+j1yVpj9dF+Xm78sXkEyZEdR6Q0mOk0EJSb8K/yTRq8sqEGI1758nslbuE0t1NE0y
+Ot9TqJwwI/h2xSLPS4K/x3x8xZsUVS8Gie4Xm2p4oXOUc6Fk8YLMIjsGELuQIr10
+KnL1CMRC3k+wsVsIsG3v0pWpQ4KHswf6aKmmtteRVLjdmr/p05/rMB91jovZMHjy
+4FSp/QZwzMMofHrdd8rdu0VgkjnGWoEUmLEmwWhCWwpWb57mLw6qKCRMtBlWxgWa
+QpSkSnvdsndD0392SRrqcjaAhUJRroGP/1i67D7/0OFsD/qmerfe7FdAnTPnqSlX
+Xvy3zyp/rRTL/OqiIEP7hh3Q8FCPFx9hMrAGr4XZbLEq6L5qZeIcqPVJM01nyYAG
+5jmWN1W2uNcLKtdhU7rA7LMBiwQ9dDHV1pbUfx3ZPDp6LC5nicX2TgwL99Skn3fu
+G+jba/QWELwEEjvtfzH+4BELTYTtNN1SiMleXKR+JFUInhHL6BCoKt9jVQUilX+l
+CD1iHn7tfZL67bWlLkNkFtkJuD4f+HFqcZdI3Kzea45oGgbSDJimI0SEtE4Q1s31
+ZCXZibgE8xMSQKZSzZ2DwRt4DrUEKQIDAQABAoICAQCpwU465XTDUTvcH/vSCJB9
+/2BYMH+OvRYDS7+qLM7+Kkxt+oWt6IEmIgfDDZTXCmWbSmXaEDS1IYzEG+qrXN6X
+rMh4Gn7MxwrvWQwp2jYDRk+u5rPJKnh4Bwd0u9u+NZKIAJcpZ7tXgcHZJs6Os/hb
+lIRP4RFQ8f5d0IKueDftXKwoyOKW2imB0m7CAHr4DajHKS+xDVMRe1Wg6IFE1YaS
+D7O6S6tXyGKFZA+QKqN7LuHKmmW1Or5URM7uf5PV6JJfQKqZzu/qLCFyYvA0AFsw
+SeMeAC5HnxIMp3KETHIA0gTCBgPJBpVWp+1D9AQPKhyJIHSShekcBi9SO0xgUB+s
+h1UEcC2zf95Vson0KySX9zWRUZkrU8/0KYhYljN2/vdW8XxkRBC0pl3xWzq2kMgz
+SscZqI/MzyeUHaQno62GRlWn+WKP2NidDfR0Td/ybge1DJX+aDIfjalfCEIbJeqm
+BHn0CZ5z1RofatDlPj4p8+f2Trpcz/JCVKbGiQXi/08ZlCwkSIiOIcBVvAFErWop
+GJOBDU3StS/MXhQVb8ZeCkPBz0TM24Sv1az/MuW4w8gavpQuBC4aD5zY/TOwG8ei
+6S1sAZ0G2uc1A0FOngNvOyYYv+LImZKkWGXrLCRsqq6o/mh3M8bCHEY/lOZW8ZpL
+FCsDOO8deVZl/OX1VtB0bQKCAQEA3qRWDlUpCAU8BKa5Z1oRUz06e5KD58t2HpG8
+ndM3UO/F1XNB/6OGMWpL/XuBKOnWIB39UzsnnEtehKURTqqAsB1K3JQ5Q/FyuXRj
++o7XnNXe5lHBL5JqBIoESDchSAooQhBlQSjLSL2lg//igk0puv08wMK7UtajkV7U
+35WDa6ks6jfoSeuVibfdobkTgfw5edirOBE2Q0U2KtGsnyAzsM6tRbtgI1Yhg7eX
+nSIc4IYgq2hNLBKsegeiz1w4M6O4CQDVYFWKHyKpdrvj/fG7YZMr6YtTkuC+QPDK
+mmQIEL/lj8E26MnPLKtnTFc06LQry2V3pLWNf4mMLPNLEupEXwKCAQEA2vyg8Npn
+EZRunIr51rYScC6U6iryDjJWCwJxwr8vGU+bkqUOHTl3EqZOi5tDeYJJ+WSBqjfW
+IWrPRFZzTITlAslZ02DQ5enS9PwgUUjl7LUEbHHh+fSNIgkVfDhsuNKFzcEaIM1X
+Dl4lI2T8jEzmBep+k8f6gNmgKBgqlCf7XraorIM5diLFzy2G10zdOQTw5hW3TsVY
+d968YpfC5j57/hCrf36ahIT7o1vxLD+L27Mm9Eiib45woWjaAR1Nc9kUjqY4yV7t
+3QOw/Id9+/Sx5tZftOBvHlFyz23e1yaI3VxsiLDO9RxJwAKyA+KOvAybE2VU28hI
+s5tAYOMV6BpEdwKCAQBqRIQyySERi/YOvkmGdC4KzhHJA7DkBXA2vRcLOdKQVjHW
+ZPIeg728fmEQ90856QrkP4w3mueYKT1PEL7HDojoBsNBr5n5vRgmPtCtulpdqJOA
+2YrdGwRxcDMFCRNgoECA7/R0enU1HhgPfiZuTUha0R6bXxcsPfjKnTn8EhAtZg1j
+KhY8mi7BEjq+Q2l1RJ9mci2fUE/XIgTtwTCkrykc/jkkLICBvU234fyC6tJftIWJ
+avpSzAL5KAXk9b55n25rFbPDDHEl1VSPsLTs8+GdfDKcgXz9gTouIwCBWreizwVS
+bUW5LQIu7w0aGhHN9JlmtuK5glKsikmW9vVhbOH/AoIBAE//O7fgwQguBh5Psqca
+CjBLBAFrQNOo1b/d27r95nHDoBx5CWfppzL75/Od+4825lkhuzB4h1Pb1e2r+yC3
+54UWEydh1c43leYC+LdY/w1yrzQCgj+yc6A8W0nuvuDhnxmj8iyLdsL752s/p/aE
+3P7KRAUuZ7eMSLJ86YkH9g8KgSHMKkCawVJG2lxqauI6iNo0kqtG8mOPzZfiwsMj
+jl4ors27bSz9+4MYwkicyjWvA4r3wcco7MI6MHF5x+KLKbRWyqXddN1pTM1jncVe
+BWNDauEDn/QeYuedxmsoW5Up/0gL9v6Zn+Nx2KAMsoHFxRzXxqEnUE+0Zlc+fbE1
+b08CggEBAMiZmWtRmfueu9NMh6mgs+cmMA1ZHmbnIbtFpVjc37lrKUcjLzGF3tmp
+zQl2wy8IcHpNv8F9aKhwAInxD49RUjyqvRD6Pru+EWN6gOPJIUVuZ6mvaf7BOxbn
+Rve63hN5k4znQ1MOqGRiUkBxYSJ5wnFyQP0/8Y6+JM5uAuRUcKVNyoGURpfMrmB3
+r+KHWltM9/5iIfiDNhwStFiuOJj1YBJVzrcAn8Zh5Q0+s1hXoOUs4doLcaPHTCTU
+3hyX78yROMcZto0pVzxgQrYz31yQ5ocy9WcOYbPbQ5gdlnBEv8d7umNY1siz2wkI
+NaEkKVO0D0jFtk37s/YqJpCsXg/B7yc=
+-----END PRIVATE KEY-----
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index 6c2846a1636bd..08785e5e6669d 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -104,5 +104,6 @@ in
   malcontent = callInstalledTest ./malcontent.nix {};
   ostree = callInstalledTest ./ostree.nix {};
   pipewire = callInstalledTest ./pipewire.nix {};
+  power-profiles-daemon = callInstalledTest ./power-profiles-daemon.nix {};
   xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {};
 }
diff --git a/nixos/tests/installed-tests/fwupd.nix b/nixos/tests/installed-tests/fwupd.nix
index a8a683a1af7b0..65614e2689d8e 100644
--- a/nixos/tests/installed-tests/fwupd.nix
+++ b/nixos/tests/installed-tests/fwupd.nix
@@ -7,6 +7,5 @@ makeInstalledTest {
     services.fwupd.enable = true;
     services.fwupd.disabledPlugins = lib.mkForce []; # don't disable test plugin
     services.fwupd.enableTestRemote = true;
-    virtualisation.memorySize = 768;
   };
 }
diff --git a/nixos/tests/installed-tests/power-profiles-daemon.nix b/nixos/tests/installed-tests/power-profiles-daemon.nix
new file mode 100644
index 0000000000000..43629a0155d24
--- /dev/null
+++ b/nixos/tests/installed-tests/power-profiles-daemon.nix
@@ -0,0 +1,9 @@
+{ pkgs, lib, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.power-profiles-daemon;
+
+  testConfig = {
+    services.power-profiles-daemon.enable = true;
+  };
+}
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 48f0f59342557..1ff3dc76f4b62 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -70,7 +70,7 @@ let
     let iface = if grubVersion == 1 then "ide" else "virtio";
         isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
         bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
-    in if !isEfi && !(pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then
+    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then
       throw "Non-EFI boot methods are only supported on i686 / x86_64"
     else ''
       def assemble_qemu_flags():
@@ -184,11 +184,12 @@ let
       with subtest("Check whether nixos-rebuild works"):
           machine.succeed("nixos-rebuild switch >&2")
 
-      with subtest("Test nixos-option"):
-          kernel_modules = machine.succeed("nixos-option boot.initrd.kernelModules")
-          assert "virtio_console" in kernel_modules
-          assert "List of modules" in kernel_modules
-          assert "qemu-guest.nix" in kernel_modules
+      # FIXME: Nix 2.4 broke nixos-option, someone has to fix it.
+      # with subtest("Test nixos-option"):
+      #     kernel_modules = machine.succeed("nixos-option boot.initrd.kernelModules")
+      #     assert "virtio_console" in kernel_modules
+      #     assert "List of modules" in kernel_modules
+      #     assert "qemu-guest.nix" in kernel_modules
 
       machine.shutdown()
 
@@ -287,7 +288,7 @@ let
           # builds stuff in the VM, needs more juice
           virtualisation.diskSize = 8 * 1024;
           virtualisation.cores = 8;
-          virtualisation.memorySize = 1536;
+          virtualisation.memorySize = 2048;
 
           # Use a small /dev/vdb as the root disk for the
           # installer. This ensures the target disk (/dev/vda) is
diff --git a/nixos/tests/invidious.nix b/nixos/tests/invidious.nix
new file mode 100644
index 0000000000000..8b831715a441f
--- /dev/null
+++ b/nixos/tests/invidious.nix
@@ -0,0 +1,81 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "invidious";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ sbruder ];
+  };
+
+  machine = { config, lib, pkgs, ... }: {
+    services.invidious = {
+      enable = true;
+    };
+
+    specialisation = {
+      nginx.configuration = {
+        services.invidious = {
+          nginx.enable = true;
+          domain = "invidious.example.com";
+        };
+        services.nginx.virtualHosts."invidious.example.com" = {
+          forceSSL = false;
+          enableACME = false;
+        };
+        networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
+      };
+      postgres-tcp.configuration = {
+        services.invidious = {
+          database = {
+            createLocally = false;
+            host = "127.0.0.1";
+            passwordFile = toString (pkgs.writeText "database-password" "correct horse battery staple");
+          };
+        };
+        # Normally not needed because when connecting to postgres over TCP/IP
+        # the database is most likely on another host.
+        systemd.services.invidious = {
+          after = [ "postgresql.service" ];
+          requires = [ "postgresql.service" ];
+        };
+        services.postgresql =
+          let
+            inherit (config.services.invidious.settings.db) dbname user;
+          in
+          {
+            enable = true;
+            initialScript = pkgs.writeText "init-postgres-with-password" ''
+              CREATE USER kemal WITH PASSWORD 'correct horse battery staple';
+              CREATE DATABASE invidious;
+              GRANT ALL PRIVILEGES ON DATABASE invidious TO kemal;
+            '';
+          };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    def curl_assert_status_code(url, code, form=None):
+        assert int(machine.succeed(f"curl -s -o /dev/null -w %{{http_code}} {'-F ' + form + ' ' if form else '''}{url}")) == code
+
+
+    def activate_specialisation(name: str):
+        machine.succeed(f"${nodes.machine.config.system.build.toplevel}/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+
+    url = "http://localhost:${toString nodes.machine.config.services.invidious.port}"
+    port = ${toString nodes.machine.config.services.invidious.port}
+
+    machine.wait_for_open_port(port)
+    curl_assert_status_code(f"{url}/search", 200)
+
+    activate_specialisation("nginx")
+    machine.wait_for_open_port(80)
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+
+    # Remove the state so the `initialScript` gets run
+    machine.succeed("systemctl stop postgresql")
+    machine.succeed("rm -r /var/lib/postgresql")
+    activate_specialisation("postgres-tcp")
+    machine.wait_for_open_port(port)
+    curl_assert_status_code(f"{url}/search", 200)
+  '';
+})
diff --git a/nixos/tests/iscsi-multipath-root.nix b/nixos/tests/iscsi-multipath-root.nix
new file mode 100644
index 0000000000000..a26fea503b62a
--- /dev/null
+++ b/nixos/tests/iscsi-multipath-root.nix
@@ -0,0 +1,267 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  let
+    initiatorName = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
+    targetName = "iqn.2003-01.org.linux-iscsi.target.x8664:sn.acf8fd9c23af";
+  in
+  {
+    name = "iscsi";
+    meta = {
+      maintainers = pkgs.lib.teams.deshaw.members;
+    };
+
+    nodes = {
+      target = { config, pkgs, lib, ... }: {
+        virtualisation.vlans = [ 1 2 ];
+        services.target = {
+          enable = true;
+          config = {
+            fabric_modules = [ ];
+            storage_objects = [
+              {
+                dev = "/dev/vdb";
+                name = "test";
+                plugin = "block";
+                write_back = true;
+                wwn = "92b17c3f-6b40-4168-b082-ceeb7b495522";
+              }
+            ];
+            targets = [
+              {
+                fabric = "iscsi";
+                tpgs = [
+                  {
+                    enable = true;
+                    attributes = {
+                      authentication = 0;
+                      generate_node_acls = 1;
+                    };
+                    luns = [
+                      {
+                        alias = "94dfe06967";
+                        alua_tg_pt_gp_name = "default_tg_pt_gp";
+                        index = 0;
+                        storage_object = "/backstores/block/test";
+                      }
+                    ];
+                    node_acls = [
+                      {
+                        mapped_luns = [
+                          {
+                            alias = "d42f5bdf8a";
+                            index = 0;
+                            tpg_lun = 0;
+                            write_protect = false;
+                          }
+                        ];
+                        node_wwn = initiatorName;
+                      }
+                    ];
+                    portals = [
+                      {
+                        ip_address = "0.0.0.0";
+                        iser = false;
+                        offload = false;
+                        port = 3260;
+                      }
+                    ];
+                    tag = 1;
+                  }
+                ];
+                wwn = targetName;
+              }
+            ];
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 3260 ];
+        networking.firewall.allowedUDPPorts = [ 3260 ];
+
+        virtualisation.memorySize = 2048;
+        virtualisation.emptyDiskImages = [ 2048 ];
+      };
+
+      initiatorAuto = { nodes, config, pkgs, ... }: {
+        virtualisation.vlans = [ 1 2 ];
+
+        services.multipath = {
+          enable = true;
+          defaults = ''
+            find_multipaths yes
+            user_friendly_names yes
+          '';
+          pathGroups = [
+            {
+              alias = 123456;
+              wwid = "3600140592b17c3f6b404168b082ceeb7";
+            }
+          ];
+        };
+
+        services.openiscsi = {
+          enable = true;
+          enableAutoLoginOut = true;
+          discoverPortal = "target";
+          name = initiatorName;
+        };
+
+        environment.systemPackages = with pkgs; [
+          xfsprogs
+        ];
+
+        environment.etc."initiator-root-disk-closure".source = nodes.initiatorRootDisk.config.system.build.toplevel;
+
+        nix.binaryCaches = lib.mkForce [ ];
+        nix.extraOptions = ''
+          hashed-mirrors =
+          connect-timeout = 1
+        '';
+      };
+
+      initiatorRootDisk = { config, pkgs, modulesPath, lib, ... }: {
+        boot.initrd.network.enable = true;
+        boot.loader.grub.enable = false;
+
+        boot.kernelParams = lib.mkOverride 5 (
+          [
+            "boot.shell_on_fail"
+            "console=tty1"
+            "ip=192.168.1.1:::255.255.255.0::ens9:none"
+            "ip=192.168.2.1:::255.255.255.0::ens10:none"
+          ]
+        );
+
+        # defaults to true, puts some code in the initrd that tries to mount an overlayfs on /nix/store
+        virtualisation.writableStore = false;
+        virtualisation.vlans = [ 1 2 ];
+
+        services.multipath = {
+          enable = true;
+          defaults = ''
+            find_multipaths yes
+            user_friendly_names yes
+          '';
+          pathGroups = [
+            {
+              alias = 123456;
+              wwid = "3600140592b17c3f6b404168b082ceeb7";
+            }
+          ];
+        };
+
+        fileSystems = lib.mkOverride 5 {
+          "/" = {
+            fsType = "xfs";
+            device = "/dev/mapper/123456";
+            options = [ "_netdev" ];
+          };
+        };
+
+        boot.initrd.extraFiles."etc/multipath/wwids".source = pkgs.writeText "wwids" "/3600140592b17c3f6b404168b082ceeb7/";
+
+        boot.iscsi-initiator = {
+          discoverPortal = "target";
+          name = initiatorName;
+          target = targetName;
+          extraIscsiCommands = ''
+            iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login
+          '';
+        };
+      };
+
+    };
+
+    testScript = { nodes, ... }: ''
+      target.start()
+      target.wait_for_unit("iscsi-target.service")
+
+      initiatorAuto.start()
+
+      initiatorAuto.wait_for_unit("iscsid.service")
+      initiatorAuto.wait_for_unit("iscsi.service")
+      initiatorAuto.get_unit_info("iscsi")
+
+      # Expecting this to fail since we should already know about 192.168.1.3
+      initiatorAuto.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
+      # Expecting this to succeed since we don't yet know about 192.168.2.3
+      initiatorAuto.succeed("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
+
+      # /dev/sda is provided by iscsi on target
+      initiatorAuto.succeed("set -x; while ! test -e /dev/sda; do sleep 1; done")
+
+      initiatorAuto.succeed("mkfs.xfs /dev/sda")
+      initiatorAuto.succeed("mkdir /mnt")
+
+      # Start by verifying /dev/sda and /dev/sdb are both the same disk
+      initiatorAuto.succeed("mount /dev/sda /mnt")
+      initiatorAuto.succeed("touch /mnt/hi")
+      initiatorAuto.succeed("umount /mnt")
+
+      initiatorAuto.succeed("mount /dev/sdb /mnt")
+      initiatorAuto.succeed("test -e /mnt/hi")
+      initiatorAuto.succeed("umount /mnt")
+
+      initiatorAuto.succeed("systemctl restart multipathd")
+      initiatorAuto.succeed("multipath -ll | systemd-cat")
+
+      # Install our RootDisk machine to 123456, the alias to the device that multipath is now managing
+      initiatorAuto.succeed("mount /dev/mapper/123456 /mnt")
+      initiatorAuto.succeed("mkdir -p /mnt/etc/{multipath,iscsi}")
+      initiatorAuto.succeed("cp -r /etc/multipath/wwids /mnt/etc/multipath/wwids")
+      initiatorAuto.succeed("cp -r /etc/iscsi/{nodes,send_targets} /mnt/etc/iscsi")
+      initiatorAuto.succeed(
+        "nixos-install --no-bootloader --no-root-passwd --system /etc/initiator-root-disk-closure"
+      )
+      initiatorAuto.succeed("umount /mnt")
+      initiatorAuto.shutdown()
+
+      initiatorRootDisk.start()
+      initiatorRootDisk.wait_for_unit("multi-user.target")
+      initiatorRootDisk.wait_for_unit("iscsid")
+
+      # Log in over both nodes
+      initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
+      initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
+      initiatorRootDisk.succeed("systemctl restart multipathd")
+      initiatorRootDisk.succeed("multipath -ll | systemd-cat")
+
+      # Verify we can write and sync the root disk
+      initiatorRootDisk.succeed("mkdir /scratch")
+      initiatorRootDisk.succeed("touch /scratch/both-up")
+      initiatorRootDisk.succeed("sync /scratch")
+
+      # Verify we can write to the root with ens9 (sda, 192.168.1.3) down
+      initiatorRootDisk.succeed("ip link set ens9 down")
+      initiatorRootDisk.succeed("touch /scratch/ens9-down")
+      initiatorRootDisk.succeed("sync /scratch")
+      initiatorRootDisk.succeed("ip link set ens9 up")
+
+      # todo: better way to wait until multipath notices the link is back
+      initiatorRootDisk.succeed("sleep 5")
+      initiatorRootDisk.succeed("touch /scratch/both-down")
+      initiatorRootDisk.succeed("sync /scratch")
+
+      # Verify we can write to the root with ens10 (sdb, 192.168.2.3) down
+      initiatorRootDisk.succeed("ip link set ens10 down")
+      initiatorRootDisk.succeed("touch /scratch/ens10-down")
+      initiatorRootDisk.succeed("sync /scratch")
+      initiatorRootDisk.succeed("ip link set ens10 up")
+      initiatorRootDisk.succeed("touch /scratch/ens10-down")
+      initiatorRootDisk.succeed("sync /scratch")
+
+      initiatorRootDisk.succeed("ip link set ens9 up")
+      initiatorRootDisk.succeed("ip link set ens10 up")
+      initiatorRootDisk.shutdown()
+
+      # Verify we can boot with the target's eth1 down, forcing
+      # it to multipath via the second link
+      target.succeed("ip link set eth1 down")
+      initiatorRootDisk.start()
+      initiatorRootDisk.wait_for_unit("multi-user.target")
+      initiatorRootDisk.wait_for_unit("iscsid")
+      initiatorRootDisk.succeed("test -e /scratch/both-up")
+    '';
+  }
+)
+
+
diff --git a/nixos/tests/jibri.nix b/nixos/tests/jibri.nix
new file mode 100644
index 0000000000000..3dd28e6aac1a3
--- /dev/null
+++ b/nixos/tests/jibri.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "jibri";
+  meta = with pkgs.lib; {
+    maintainers = teams.jitsi.members;
+  };
+
+    machine = { config, pkgs, ... }: {
+      virtualisation.memorySize = 5120;
+
+      services.jitsi-meet = {
+        enable = true;
+        hostName = "machine";
+        jibri.enable = true;
+      };
+      services.jibri.ignoreCert = true;
+      services.jitsi-videobridge.openFirewall = true;
+
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      services.nginx.virtualHosts.machine = {
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      security.acme.email = "me@example.org";
+      security.acme.acceptTerms = true;
+      security.acme.server = "https://example.com"; # self-signed only
+    };
+
+  testScript = ''
+    machine.wait_for_unit("jitsi-videobridge2.service")
+    machine.wait_for_unit("jicofo.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("prosody.service")
+    machine.wait_for_unit("jibri.service")
+
+    machine.wait_until_succeeds(
+        "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'", timeout=30
+    )
+    machine.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.machine'", timeout=31
+    )
+    machine.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.machine'", timeout=32
+    )
+    machine.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jibri@auth.machine'", timeout=33
+    )
+    machine.wait_until_succeeds(
+        "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Joined MUC: jibribrewery@internal.machine'", timeout=34
+    )
+
+    assert '"busyStatus":"IDLE","health":{"healthStatus":"HEALTHY"' in machine.succeed(
+        "curl -X GET http://machine:2222/jibri/api/v1.0/health"
+    )
+    machine.succeed(
+        """curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/startService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'"""
+    )
+    machine.wait_until_succeeds(
+        "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'File recording service transitioning from state Starting up to Running'", timeout=35
+    )
+    machine.succeed(
+        """sleep 15 && curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/stopService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'"""
+    )
+    machine.wait_until_succeeds(
+        "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Recording finalize script finished with exit value 0'", timeout=36
+    )
+  '';
+})
diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix
index f9a0b121a2bfc..d95f7c2ea9eaa 100644
--- a/nixos/tests/jitsi-meet.nix
+++ b/nixos/tests/jitsi-meet.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     client = { nodes, pkgs, ... }: {
     };
     server = { config, pkgs, ... }: {
-      virtualisation.memorySize = 512;
       services.jitsi-meet = {
         enable = true;
         hostName = "server";
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
index 95711808a2c35..5def759ca24d9 100644
--- a/nixos/tests/kafka.nix
+++ b/nixos/tests/kafka.nix
@@ -19,7 +19,6 @@ let
         };
 
         networking.firewall.allowedTCPPorts = [ 2181 ];
-        virtualisation.memorySize = 1024;
       };
       kafka = { ... }: {
         services.apache-kafka = {
diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix
index 98902187f6ac3..685a200b31878 100644
--- a/nixos/tests/keepassxc.nix
+++ b/nixos/tests/keepassxc.nix
@@ -26,7 +26,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     machine.wait_for_x()
 
     # start KeePassXC window
-    machine.execute("su - alice -c keepassxc &")
+    machine.execute("su - alice -c keepassxc >&2 &")
 
     machine.wait_for_text("KeePassXC ${pkgs.keepassxc.version}")
     machine.screenshot("KeePassXC")
diff --git a/nixos/tests/kerberos/heimdal.nix b/nixos/tests/kerberos/heimdal.nix
index 8abae667d043f..391a61cc9a90b 100644
--- a/nixos/tests/kerberos/heimdal.nix
+++ b/nixos/tests/kerberos/heimdal.nix
@@ -9,7 +9,7 @@ import ../make-test-python.nix ({pkgs, ...}: {
     };
     krb5 = {
       enable = true;
-      kerberos = pkgs.heimdalFull;
+      kerberos = pkgs.heimdal;
       libdefaults = {
         default_realm = "FOO.BAR";
       };
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 391a93e369814..45c5c1963a0db 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -23,22 +23,15 @@ let
         assert "${linuxPackages.kernel.modDirVersion}" in machine.succeed("uname -a")
       '';
   }) args);
-  kernels = {
-    inherit (pkgs)
-      linuxPackages_4_4
-      linuxPackages_4_9
-      linuxPackages_4_14
-      linuxPackages_4_19
-      linuxPackages_5_4
-      linuxPackages_5_10
-      linuxPackages_5_13
-
-      linuxPackages_4_14_hardened
-      linuxPackages_4_19_hardened
-      linuxPackages_5_4_hardened
-      linuxPackages_5_10_hardened
-
-      linuxPackages_testing;
+  kernels = pkgs.linuxKernel.vanillaPackages // {
+    inherit (pkgs.linuxKernel.packages)
+      linux_4_14_hardened
+      linux_4_19_hardened
+      linux_5_4_hardened
+      linux_5_10_hardened
+      linux_5_15_hardened
+
+      linux_testing;
   };
 
 in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // {
diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix
index ec0cd9796b0e2..010f3da49846a 100644
--- a/nixos/tests/kexec.nix
+++ b/nixos/tests/kexec.nix
@@ -4,12 +4,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   name = "kexec";
   meta = with lib.maintainers; {
     maintainers = [ eelco ];
-    # Currently hangs forever; last output is:
-    #     machine # [   10.239914] dhcpcd[707]: eth0: adding default route via fe80::2
-    #     machine: waiting for the VM to finish booting
-    #     machine # Cannot find the ESP partition mount point.
-    #     machine # [   28.681197] nscd[692]: 692 checking for monitored file `/etc/netgroup': No such file or directory
-    broken = true;
   };
 
   machine = { ... }:
@@ -18,8 +12,11 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   testScript =
     ''
       machine.wait_for_unit("multi-user.target")
-      machine.execute("systemctl kexec &")
+      machine.succeed('kexec --load /run/current-system/kernel --initrd /run/current-system/initrd --command-line "$(</proc/cmdline)"')
+      machine.execute("systemctl kexec >&2 &", check_return=False)
       machine.connected = False
+      machine.connect()
       machine.wait_for_unit("multi-user.target")
+      machine.shutdown()
     '';
 })
diff --git a/nixos/tests/keycloak.nix b/nixos/tests/keycloak.nix
index fc321b8902f1d..1be3fed6acc9d 100644
--- a/nixos/tests/keycloak.nix
+++ b/nixos/tests/keycloak.nix
@@ -17,7 +17,6 @@ let
 
       nodes = {
         keycloak = { ... }: {
-          virtualisation.memorySize = 1024;
 
           security.pki.certificateFiles = [
             certs.ca.cert
diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix
index a18a05f90c6d3..4306a9ae2cf94 100644
--- a/nixos/tests/keymap.nix
+++ b/nixos/tests/keymap.nix
@@ -46,7 +46,7 @@ let
 
               # set up process that expects all the keys to be entered
               machine.succeed(
-                  "{} {} {} {} &".format(
+                  "{} {} {} {} >&2 &".format(
                       cmd,
                       "${testReader}",
                       len(inputs),
diff --git a/nixos/tests/libinput.nix b/nixos/tests/libinput.nix
new file mode 100644
index 0000000000000..2f84aaadcd0be
--- /dev/null
+++ b/nixos/tests/libinput.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ ... }:
+
+{
+  name = "libinput";
+
+  machine = { ... }:
+    {
+      imports = [
+        ./common/x11.nix
+        ./common/user-account.nix
+      ];
+
+      test-support.displayManager.auto.user = "alice";
+
+      services.xserver.libinput = {
+        enable = true;
+        mouse = {
+          naturalScrolling = true;
+          leftHanded = true;
+          middleEmulation = false;
+          horizontalScrolling = false;
+        };
+      };
+    };
+
+  testScript = ''
+    def expect_xserver_option(option, value):
+        machine.succeed(f"""cat /var/log/X.0.log | grep -F 'Option "{option}" "{value}"'""")
+
+    machine.start()
+    machine.wait_for_x()
+    machine.succeed("""cat /var/log/X.0.log | grep -F "Using input driver 'libinput'" """)
+    expect_xserver_option("NaturalScrolling", "on")
+    expect_xserver_option("LeftHanded", "on")
+    expect_xserver_option("MiddleEmulation", "off")
+    expect_xserver_option("HorizontalScrolling", "off")
+  '';
+})
diff --git a/nixos/tests/libresprite.nix b/nixos/tests/libresprite.nix
new file mode 100644
index 0000000000000..1a6210e3671ae
--- /dev/null
+++ b/nixos/tests/libresprite.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "libresprite";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [
+      pkgs.imagemagick
+      pkgs.libresprite
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.succeed("convert -font DejaVu-Sans +antialias label:'IT WORKS' image.png")
+      machine.execute("libresprite image.png >&2 &")
+      machine.wait_for_window("LibreSprite v${pkgs.libresprite.version}")
+      machine.wait_for_text("IT WORKS")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixos/tests/libreswan.nix b/nixos/tests/libreswan.nix
index 17ae60af8eed4..56ab908aed9a3 100644
--- a/nixos/tests/libreswan.nix
+++ b/nixos/tests/libreswan.nix
@@ -89,7 +89,7 @@ in
           """
           Sends a message as Alice to Bob
           """
-          bob.execute("nc -lu ::0 1234 >/tmp/msg &")
+          bob.execute("nc -lu ::0 1234 >/tmp/msg >&2 &")
           alice.sleep(1)
           alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234")
           bob.succeed(f"grep '{msg}' /tmp/msg")
@@ -100,7 +100,7 @@ in
           Starts eavesdropping on Alice and Bob
           """
           match = "src host alice and dst host bob"
-          eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &")
+          eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log >&2 &")
 
 
       start_all()
@@ -120,7 +120,7 @@ in
           alice.succeed("ipsec verify 1>&2")
 
       with subtest("Alice and Bob can start the tunnel"):
-          alice.execute("ipsec auto --start tunnel &")
+          alice.execute("ipsec auto --start tunnel >&2 &")
           bob.succeed("ipsec auto --start tunnel")
           # apparently this is needed to "wake" the tunnel
           bob.execute("ping -c1 alice")
diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix
index c33c7503993da..147ae999fdb1b 100644
--- a/nixos/tests/lorri/default.nix
+++ b/nixos/tests/lorri/default.nix
@@ -14,7 +14,7 @@ import ../make-test-python.nix {
     )
 
     # Start the daemon and wait until it is ready
-    machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr &")
+    machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr >&2 &")
     machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout")
 
     # Ping the daemon
diff --git a/nixos/tests/lsd.nix b/nixos/tests/lsd.nix
deleted file mode 100644
index c643f2f0b7b7d..0000000000000
--- a/nixos/tests/lsd.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "lsd";
-  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
-
-  nodes.lsd = { pkgs, ... }: { environment.systemPackages = [ pkgs.lsd ]; };
-
-  testScript = ''
-    lsd.succeed('echo "abc" > /tmp/foo')
-    assert "4 B /tmp/foo" in lsd.succeed('lsd --classic --blocks "size,name" -l /tmp/foo')
-    assert "lsd ${pkgs.lsd.version}" in lsd.succeed("lsd --version")
-  '';
-})
diff --git a/nixos/tests/lxd-image-server.nix b/nixos/tests/lxd-image-server.nix
new file mode 100644
index 0000000000000..9f060fed38d87
--- /dev/null
+++ b/nixos/tests/lxd-image-server.nix
@@ -0,0 +1,127 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  # Since we don't have access to the internet during the tests, we have to
+  # pre-fetch lxd containers beforehand.
+  #
+  # I've chosen to import Alpine Linux, because its image is turbo-tiny and,
+  # generally, sufficient for our tests.
+  alpine-meta = pkgs.fetchurl {
+    url = "https://tarballs.nixos.org/alpine/3.12/lxd.tar.xz";
+    hash = "sha256-1tcKaO9lOkvqfmG/7FMbfAEToAuFy2YMewS8ysBKuLA=";
+  };
+
+  alpine-rootfs = pkgs.fetchurl {
+    url = "https://tarballs.nixos.org/alpine/3.12/rootfs.tar.xz";
+    hash = "sha256-Tba9sSoaiMtQLY45u7p5DMqXTSDgs/763L/SQp0bkCA=";
+  };
+
+  lxd-config = pkgs.writeText "config.yaml" ''
+    storage_pools:
+      - name: default
+        driver: dir
+        config:
+          source: /var/lxd-pool
+
+    networks:
+      - name: lxdbr0
+        type: bridge
+        config:
+          ipv4.address: auto
+          ipv6.address: none
+
+    profiles:
+      - name: default
+        devices:
+          eth0:
+            name: eth0
+            network: lxdbr0
+            type: nic
+          root:
+            path: /
+            pool: default
+            type: disk
+  '';
+
+
+in {
+  name = "lxd-image-server";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mkg20001 ];
+  };
+
+  machine = { lib, ... }: {
+    virtualisation = {
+      cores = 2;
+
+      memorySize = 2048;
+      diskSize = 4096;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+
+    security.pki.certificates = [
+      (builtins.readFile ./common/acme/server/ca.cert.pem)
+    ];
+
+    services.nginx = {
+      enable = true;
+    };
+
+    services.lxd-image-server = {
+      enable = true;
+      nginx = {
+        enable = true;
+        domain = "acme.test";
+      };
+    };
+
+    services.nginx.virtualHosts."acme.test" = {
+      enableACME = false;
+      sslCertificate = ./common/acme/server/acme.test.cert.pem;
+      sslCertificateKey = ./common/acme/server/acme.test.key.pem;
+    };
+
+    networking.hosts = {
+      "::1" = [ "acme.test" ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+    # It takes additional second for lxd to settle
+    machine.sleep(1)
+
+    # lxd expects the pool's directory to already exist
+    machine.succeed("mkdir /var/lxd-pool")
+
+
+    machine.succeed(
+        "cat ${lxd-config} | lxd init --preseed"
+    )
+
+    machine.succeed(
+        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+    )
+
+    loc = "/var/www/simplestreams/images/iats/alpine/amd64/default/v1"
+
+    with subtest("push image to server"):
+        machine.succeed("lxc launch alpine test")
+        machine.succeed("lxc stop test")
+        machine.succeed("lxc publish --public test --alias=testimg")
+        machine.succeed("lxc image export testimg")
+        machine.succeed("ls >&2")
+        machine.succeed("mkdir -p " + loc)
+        machine.succeed("mv *.tar.gz " + loc)
+
+    with subtest("pull image from server"):
+        machine.succeed("lxc remote add img https://acme.test --protocol=simplestreams")
+        machine.succeed("lxc image list img: >&2")
+  '';
+})
diff --git a/nixos/tests/lxd-image.nix b/nixos/tests/lxd-image.nix
new file mode 100644
index 0000000000000..096b9d9aba906
--- /dev/null
+++ b/nixos/tests/lxd-image.nix
@@ -0,0 +1,89 @@
+# This test ensures that the nixOS lxd images builds and functions properly
+# It has been extracted from `lxd.nix` to seperate failures of just the image and the lxd software
+
+import ./make-test-python.nix ({ pkgs, ...} : let
+  release = import ../release.nix {
+    /* configuration = {
+      environment.systemPackages = with pkgs; [ stdenv ]; # inject stdenv so rebuild test works
+    }; */
+  };
+
+  metadata = release.lxdMeta.${pkgs.system};
+  image = release.lxdImage.${pkgs.system};
+
+  lxd-config = pkgs.writeText "config.yaml" ''
+    storage_pools:
+      - name: default
+        driver: dir
+        config:
+          source: /var/lxd-pool
+
+    networks:
+      - name: lxdbr0
+        type: bridge
+        config:
+          ipv4.address: auto
+          ipv6.address: none
+
+    profiles:
+      - name: default
+        devices:
+          eth0:
+            name: eth0
+            network: lxdbr0
+            type: nic
+          root:
+            path: /
+            pool: default
+            type: disk
+  '';
+in {
+  name = "lxd-image";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mkg20001 ];
+  };
+
+  machine = { lib, ... }: {
+    virtualisation = {
+      # disk full otherwise
+      diskSize = 2048;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+    # It takes additional second for lxd to settle
+    machine.sleep(1)
+
+    # lxd expects the pool's directory to already exist
+    machine.succeed("mkdir /var/lxd-pool")
+
+    machine.succeed(
+        "cat ${lxd-config} | lxd init --preseed"
+    )
+
+    # TODO: test custom built container aswell
+
+    with subtest("importing container works"):
+        machine.succeed("lxc image import ${metadata}/*/*.tar.xz ${image}/*/*.tar.xz --alias nixos")
+
+    with subtest("launching container works"):
+        machine.succeed("lxc launch nixos machine -c security.nesting=true")
+        # make sure machine boots up properly
+        machine.sleep(5)
+
+    with subtest("container shell works"):
+        machine.succeed("echo true | lxc exec machine /run/current-system/sw/bin/bash -")
+        machine.succeed("lxc exec machine /run/current-system/sw/bin/true")
+
+    # with subtest("rebuilding works"):
+    #     machine.succeed("lxc exec machine /run/current-system/sw/bin/nixos-rebuild switch")
+  '';
+})
diff --git a/nixos/tests/lxd.nix b/nixos/tests/lxd.nix
index 889ca9598e3f3..1a3b84a85cf68 100644
--- a/nixos/tests/lxd.nix
+++ b/nixos/tests/lxd.nix
@@ -133,9 +133,5 @@ in {
         )
 
         machine.succeed("lxc delete -f test")
-
-    with subtest("Unless explicitly changed, lxd leans on iptables"):
-        machine.succeed("lsmod | grep ip_tables")
-        machine.fail("lsmod | grep nf_tables")
   '';
 })
diff --git a/nixos/tests/magic-wormhole-mailbox-server.nix b/nixos/tests/magic-wormhole-mailbox-server.nix
index afdf7124fdc56..54088ac60f284 100644
--- a/nixos/tests/magic-wormhole-mailbox-server.nix
+++ b/nixos/tests/magic-wormhole-mailbox-server.nix
@@ -29,7 +29,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     # Create a secret file and send it to Bob
     client_alice.succeed("echo mysecret > secretfile")
-    client_alice.succeed("wormhole --relay-url=ws://server:4000/v1 send -0 secretfile &")
+    client_alice.succeed("wormhole --relay-url=ws://server:4000/v1 send -0 secretfile >&2 &")
 
     # Retrieve a secret file from Alice and check its content
     client_bob.succeed("wormhole --relay-url=ws://server:4000/v1 receive -0 --accept-file")
diff --git a/nixos/tests/matrix-appservice-irc.nix b/nixos/tests/matrix-appservice-irc.nix
index 79b07ef83c578..e1da410af0607 100644
--- a/nixos/tests/matrix-appservice-irc.nix
+++ b/nixos/tests/matrix-appservice-irc.nix
@@ -25,7 +25,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
                 "bind_address" = "";
                 "port" = 8448;
                 "resources" = [
-                  { "compress" = true; "names" = [ "client" "webclient" ]; }
+                  { "compress" = true; "names" = [ "client" ]; }
                   { "compress" = false; "names" = [ "federation" ]; }
                 ];
                 "tls" = false;
@@ -85,52 +85,108 @@ import ./make-test-python.nix ({ pkgs, ... }:
       client = { pkgs, ... }: {
         environment.systemPackages = [
           (pkgs.writers.writePython3Bin "do_test"
-            { libraries = [ pkgs.python3Packages.matrix-client ]; } ''
-            import socket
-            from matrix_client.client import MatrixClient
-            from time import sleep
-
-            matrix = MatrixClient("${homeserverUrl}")
-            matrix.register_with_password(username="alice", password="foobar")
-
-            irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            irc.connect(("ircd", 6667))
-            irc.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
-            irc.send(b"USER bob bob bob :bob\n")
-            irc.send(b"NICK bob\n")
-
-            m_room = matrix.join_room("#irc_#test:homeserver")
-            irc.send(b"JOIN #test\n")
-
-            # plenty of time for the joins to happen
-            sleep(10)
-
-            m_room.send_text("hi from matrix")
-            irc.send(b"PRIVMSG #test :hi from irc \r\n")
-
-            print("Waiting for irc message...")
-            while True:
-                buf = irc.recv(10000)
-                if b"hi from matrix" in buf:
-                    break
-
-            print("Waiting for matrix message...")
-
-
-            def callback(room, e):
-                if "hi from irc" in e['content']['body']:
-                    exit(0)
-
-
-            m_room.add_listener(callback, "m.room.message")
-            matrix.listen_forever()
-          ''
+          {
+            libraries = [ pkgs.python3Packages.matrix-nio ];
+            flakeIgnore = [
+              # We don't live in the dark ages anymore.
+              # Languages like Python that are whitespace heavy will overrun
+              # 79 characters..
+              "E501"
+            ];
+          } ''
+              import sys
+              import socket
+              import functools
+              from time import sleep
+              import asyncio
+
+              from nio import AsyncClient, RoomMessageText, JoinResponse
+
+
+              async def matrix_room_message_text_callback(matrix: AsyncClient, msg: str, _r, e):
+                  print("Received matrix text message: ", e)
+                  if msg in e.body:
+                      print("Received hi from IRC")
+                      await matrix.close()
+                      exit(0)  # Actual exit point
+
+
+              class IRC:
+                  def __init__(self):
+                      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                      sock.connect(("ircd", 6667))
+                      sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+                      sock.send(b"USER bob bob bob :bob\n")
+                      sock.send(b"NICK bob\n")
+                      self.sock = sock
+
+                  def join(self, room: str):
+                      self.sock.send(f"JOIN {room}\n".encode())
+
+                  def privmsg(self, room: str, msg: str):
+                      self.sock.send(f"PRIVMSG {room} :{msg}\n".encode())
+
+                  def expect_msg(self, body: str):
+                      buffer = ""
+                      while True:
+                          buf = self.sock.recv(1024).decode()
+                          buffer += buf
+                          if body in buffer:
+                              return
+
+
+              async def run(homeserver: str):
+                  irc = IRC()
+
+                  matrix = AsyncClient(homeserver)
+                  response = await matrix.register("alice", "foobar")
+                  print("Matrix register response: ", response)
+
+                  response = await matrix.join("#irc_#test:homeserver")
+                  print("Matrix join room response:", response)
+                  assert isinstance(response, JoinResponse)
+                  room_id = response.room_id
+
+                  irc.join("#test")
+                  # FIXME: what are we waiting on here? Matrix? IRC? Both?
+                  # 10s seem bad for busy hydra machines.
+                  sleep(10)
+
+                  # Exchange messages
+                  print("Sending text message to matrix room")
+                  response = await matrix.room_send(
+                      room_id=room_id,
+                      message_type="m.room.message",
+                      content={"msgtype": "m.text", "body": "hi from matrix"},
+                  )
+                  print("Matrix room send response: ", response)
+                  irc.privmsg("#test", "hi from irc")
+
+                  print("Waiting for the matrix message to appear on the IRC side...")
+                  irc.expect_msg("hi from matrix")
+
+                  callback = functools.partial(
+                      matrix_room_message_text_callback, matrix, "hi from irc"
+                  )
+                  matrix.add_event_callback(callback, RoomMessageText)
+
+                  print("Waiting for matrix message...")
+                  await matrix.sync_forever()
+
+                  exit(1)  # Unreachable
+
+
+              if __name__ == "__main__":
+                  asyncio.run(run(sys.argv[1]))
+            ''
           )
         ];
       };
     };
 
     testScript = ''
+      import pathlib
+
       start_all()
 
       ircd.wait_for_unit("ngircd.service")
@@ -156,7 +212,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
           homeserver.wait_for_open_port(8448)
 
       with subtest("ensure messages can be exchanged"):
-          client.succeed("do_test")
+          client.succeed("do_test ${homeserverUrl} >&2")
     '';
-
   })
diff --git a/nixos/tests/matrix/mjolnir.nix b/nixos/tests/matrix/mjolnir.nix
new file mode 100644
index 0000000000000..bb55f6f5440b2
--- /dev/null
+++ b/nixos/tests/matrix/mjolnir.nix
@@ -0,0 +1,165 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+  let
+    # Set up SSL certs for Synapse to be happy.
+    runWithOpenSSL = file: cmd: pkgs.runCommand file
+      {
+        buildInputs = [ pkgs.openssl ];
+      }
+      cmd;
+
+    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+    ca_pem = runWithOpenSSL "ca.pem" ''
+      openssl req \
+        -x509 -new -nodes -key ${ca_key} \
+        -days 10000 -out $out -subj "/CN=snakeoil-ca"
+    '';
+    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+    csr = runWithOpenSSL "matrix.csr" ''
+      openssl req \
+         -new -key ${key} \
+         -out $out -subj "/CN=localhost" \
+    '';
+    cert = runWithOpenSSL "matrix_cert.pem" ''
+      openssl x509 \
+        -req -in ${csr} \
+        -CA ${ca_pem} -CAkey ${ca_key} \
+        -CAcreateserial -out $out \
+        -days 365
+    '';
+  in
+  {
+    name = "mjolnir";
+    meta = with pkgs.lib; {
+      maintainers = teams.matrix.members;
+    };
+
+    nodes = {
+      homeserver = { pkgs, ... }: {
+        services.matrix-synapse = {
+          enable = true;
+          database_type = "sqlite3";
+          tls_certificate_path = "${cert}";
+          tls_private_key_path = "${key}";
+          enable_registration = true;
+          registration_shared_secret = "supersecret-registration";
+
+          listeners = [
+            # The default but tls=false
+            {
+              "bind_address" = "";
+              "port" = 8448;
+              "resources" = [
+                { "compress" = true; "names" = [ "client" "webclient" ]; }
+                { "compress" = false; "names" = [ "federation" ]; }
+              ];
+              "tls" = false;
+              "type" = "http";
+              "x_forwarded" = false;
+            }
+          ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 8448 ];
+
+        environment.systemPackages = [
+          (pkgs.writeShellScriptBin "register_mjolnir_user" ''
+            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
+              -u mjolnir \
+              -p mjolnir-password \
+              --admin \
+              --shared-secret supersecret-registration \
+              http://localhost:8448
+          ''
+          )
+          (pkgs.writeShellScriptBin "register_moderator_user" ''
+            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
+              -u moderator \
+              -p moderator-password \
+              --no-admin \
+              --shared-secret supersecret-registration \
+              http://localhost:8448
+          ''
+          )
+        ];
+      };
+
+      mjolnir = { pkgs, ... }: {
+        services.mjolnir = {
+          enable = true;
+          homeserverUrl = "http://homeserver:8448";
+          pantalaimon = {
+            enable = true;
+            username = "mjolnir";
+            passwordFile = pkgs.writeText "password.txt" "mjolnir-password";
+          };
+          managementRoom = "#moderators:homeserver";
+        };
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (pkgs.writers.writePython3Bin "create_management_room_and_invite_mjolnir"
+            { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
+            import asyncio
+
+            from nio import (
+                AsyncClient,
+                EnableEncryptionBuilder
+            )
+
+
+            async def main() -> None:
+                client = AsyncClient("http://homeserver:8448", "moderator")
+
+                await client.login("moderator-password")
+
+                room = await client.room_create(
+                    name="Moderators",
+                    alias="moderators",
+                    initial_state=[EnableEncryptionBuilder().as_dict()],
+                )
+
+                await client.join(room.room_id)
+                await client.room_invite(room.room_id, "@mjolnir:homeserver")
+
+            asyncio.run(main())
+          ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      with subtest("start homeserver"):
+        homeserver.start()
+
+        homeserver.wait_for_unit("matrix-synapse.service")
+        homeserver.wait_until_succeeds("curl --fail -L http://localhost:8448/")
+
+      with subtest("register users"):
+        # register mjolnir user
+        homeserver.succeed("register_mjolnir_user")
+        # register moderator user
+        homeserver.succeed("register_moderator_user")
+
+      with subtest("start mjolnir"):
+        mjolnir.start()
+
+        # wait for pantalaimon to be ready
+        mjolnir.wait_for_unit("pantalaimon-mjolnir.service")
+        mjolnir.wait_for_unit("mjolnir.service")
+
+        mjolnir.wait_until_succeeds("curl --fail -L http://localhost:8009/")
+
+      with subtest("ensure mjolnir can be invited to the management room"):
+        client.start()
+
+        client.wait_until_succeeds("curl --fail -L http://homeserver:8448/")
+
+        client.succeed("create_management_room_and_invite_mjolnir")
+
+        mjolnir.wait_for_console_text("Startup complete. Now monitoring rooms")
+    '';
+  }
+)
diff --git a/nixos/tests/matrix/pantalaimon.nix b/nixos/tests/matrix/pantalaimon.nix
new file mode 100644
index 0000000000000..fcb9904b21388
--- /dev/null
+++ b/nixos/tests/matrix/pantalaimon.nix
@@ -0,0 +1,65 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+  let
+    pantalaimonInstanceName = "testing";
+
+    # Set up SSL certs for Synapse to be happy.
+    runWithOpenSSL = file: cmd: pkgs.runCommand file
+      {
+        buildInputs = [ pkgs.openssl ];
+      }
+      cmd;
+
+    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+    ca_pem = runWithOpenSSL "ca.pem" ''
+      openssl req \
+        -x509 -new -nodes -key ${ca_key} \
+        -days 10000 -out $out -subj "/CN=snakeoil-ca"
+    '';
+    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+    csr = runWithOpenSSL "matrix.csr" ''
+      openssl req \
+         -new -key ${key} \
+         -out $out -subj "/CN=localhost" \
+    '';
+    cert = runWithOpenSSL "matrix_cert.pem" ''
+      openssl x509 \
+        -req -in ${csr} \
+        -CA ${ca_pem} -CAkey ${ca_key} \
+        -CAcreateserial -out $out \
+        -days 365
+    '';
+  in
+  {
+    name = "pantalaimon";
+    meta = with pkgs.lib; {
+      maintainers = teams.matrix.members;
+    };
+
+    machine = { pkgs, ... }: {
+      services.pantalaimon-headless.instances.${pantalaimonInstanceName} = {
+        homeserver = "https://localhost:8448";
+        listenAddress = "0.0.0.0";
+        listenPort = 8888;
+        logLevel = "debug";
+        ssl = false;
+      };
+
+      services.matrix-synapse = {
+        enable = true;
+        database_type = "sqlite3";
+        tls_certificate_path = "${cert}";
+        tls_private_key_path = "${key}";
+      };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("pantalaimon-${pantalaimonInstanceName}.service")
+      machine.wait_for_unit("matrix-synapse.service")
+      machine.wait_until_succeeds(
+          "curl --fail -L http://localhost:8888/"
+      )
+    '';
+  }
+)
diff --git a/nixos/tests/meilisearch.nix b/nixos/tests/meilisearch.nix
new file mode 100644
index 0000000000000..c379bda74c59a
--- /dev/null
+++ b/nixos/tests/meilisearch.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    listenAddress = "127.0.0.1";
+    listenPort = 7700;
+    apiUrl = "http://${listenAddress}:${toString listenPort}";
+    uid = "movies";
+    indexJSON = pkgs.writeText "index.json" (builtins.toJSON { inherit uid; });
+    moviesJSON = pkgs.runCommand "movies.json" {} ''
+      sed -n '1,5p;$p' ${pkgs.meilisearch.src}/datasets/movies/movies.json > $out
+    '';
+  in {
+    name = "meilisearch";
+    meta.maintainers = with lib.maintainers; [ Br1ght0ne ];
+
+    machine = { ... }: {
+      environment.systemPackages = with pkgs; [ curl jq ];
+      services.meilisearch = {
+        enable = true;
+        inherit listenAddress listenPort;
+      };
+    };
+
+    testScript = ''
+      import json
+
+      start_all()
+
+      machine.wait_for_unit("meilisearch")
+      machine.wait_for_open_port("7700")
+
+      with subtest("check version"):
+          version = json.loads(machine.succeed("curl ${apiUrl}/version"))
+          assert version["pkgVersion"] == "${pkgs.meilisearch.version}"
+
+      with subtest("create index"):
+          machine.succeed(
+              "curl -XPOST ${apiUrl}/indexes --data @${indexJSON}"
+          )
+          indexes = json.loads(machine.succeed("curl ${apiUrl}/indexes"))
+          assert len(indexes) == 1, "index wasn't created"
+
+      with subtest("add documents"):
+          response = json.loads(
+              machine.succeed(
+                  "curl -XPOST ${apiUrl}/indexes/${uid}/documents --data @${moviesJSON}"
+              )
+          )
+          update_id = response["updateId"]
+          machine.wait_until_succeeds(
+              f"curl ${apiUrl}/indexes/${uid}/updates/{update_id} | jq -e '.status == \"processed\"'"
+          )
+
+      with subtest("search"):
+          response = json.loads(
+              machine.succeed("curl ${apiUrl}/indexes/movies/search?q=hero")
+          )
+          print(response)
+          assert len(response["hits"]) >= 1, "no results found"
+    '';
+  })
diff --git a/nixos/tests/metabase.nix b/nixos/tests/metabase.nix
index 370114e922230..1b25071902e97 100644
--- a/nixos/tests/metabase.nix
+++ b/nixos/tests/metabase.nix
@@ -7,7 +7,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   nodes = {
     machine = { ... }: {
       services.metabase.enable = true;
-      virtualisation.memorySize = 1024;
     };
   };
 
diff --git a/nixos/tests/minecraft.nix b/nixos/tests/minecraft.nix
index 3225ebac392ab..1c34f04b4df22 100644
--- a/nixos/tests/minecraft.nix
+++ b/nixos/tests/minecraft.nix
@@ -20,7 +20,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     let user = nodes.client.config.users.users.alice;
     in ''
       client.wait_for_x()
-      client.execute("su - alice -c minecraft-launcher &")
+      client.execute("su - alice -c minecraft-launcher >&2 &")
       client.wait_for_text("Create a new Microsoft account")
       client.sleep(10)
       client.screenshot("launcher")
diff --git a/nixos/tests/minio.nix b/nixos/tests/minio.nix
index e49c517098aea..ad51f738d4901 100644
--- a/nixos/tests/minio.nix
+++ b/nixos/tests/minio.nix
@@ -28,7 +28,10 @@ in {
     machine = { pkgs, ... }: {
       services.minio = {
         enable = true;
-        inherit accessKey secretKey;
+        rootCredentialsFile = pkgs.writeText "minio-credentials" ''
+          MINIO_ROOT_USER=${accessKey}
+          MINIO_ROOT_PASSWORD=${secretKey}
+        '';
       };
       environment.systemPackages = [ pkgs.minio-client ];
 
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index fb19b7060562f..0587912c9a226 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -50,17 +50,18 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
 
 
       def get_path_info(path):
-          result = machine.succeed(f"nix path-info --json {path}")
+          result = machine.succeed(f"nix --option experimental-features nix-command path-info --json {path}")
           parsed = json.loads(result)
           return parsed
 
 
       with subtest("nix-db"):
           info = get_path_info("${foo}")
+          print(info)
 
           if (
               info[0]["narHash"]
-              != "sha256:0afw0d9j1hvwiz066z93jiddc33nxg6i6qyp26vnqyglpyfivlq5"
+              != "sha256-BdMdnb/0eWy3EddjE83rdgzWWpQjfWPAj3zDIFMD3Ck="
           ):
               raise Exception("narHash not set")
 
diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix
index e29bd559ed9bb..36cc8e3e3d9bd 100644
--- a/nixos/tests/mosquitto.nix
+++ b/nixos/tests/mosquitto.nix
@@ -2,13 +2,60 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
 let
   port = 1888;
-  username = "mqtt";
+  tlsPort = 1889;
+  anonPort = 1890;
   password = "VERY_secret";
+  hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
   topic = "test/foo";
+
+  snakeOil = pkgs.runCommand "snakeoil-certs" {
+    buildInputs = [ pkgs.gnutls.bin ];
+    caTemplate = pkgs.writeText "snakeoil-ca.template" ''
+      cn = server
+      expiration_days = -1
+      cert_signing_key
+      ca
+    '';
+    certTemplate = pkgs.writeText "snakeoil-cert.template" ''
+      cn = server
+      expiration_days = -1
+      tls_www_server
+      encryption_key
+      signing_key
+    '';
+    userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" ''
+      organization = snakeoil
+      cn = client1
+      expiration_days = -1
+      tls_www_client
+      encryption_key
+      signing_key
+    '';
+  } ''
+    mkdir "$out"
+
+    certtool -p --bits 2048 --outfile "$out/ca.key"
+    certtool -s --template "$caTemplate" --load-privkey "$out/ca.key" \
+                --outfile "$out/ca.crt"
+    certtool -p --bits 2048 --outfile "$out/server.key"
+    certtool -c --template "$certTemplate" \
+                --load-ca-privkey "$out/ca.key" \
+                --load-ca-certificate "$out/ca.crt" \
+                --load-privkey "$out/server.key" \
+                --outfile "$out/server.crt"
+
+    certtool -p --bits 2048 --outfile "$out/client1.key"
+    certtool -c --template "$userCertTemplate" \
+                --load-privkey "$out/client1.key" \
+                --load-ca-privkey "$out/ca.key" \
+                --load-ca-certificate "$out/ca.crt" \
+                --outfile "$out/client1.crt"
+  '';
+
 in {
   name = "mosquitto";
   meta = with pkgs.lib; {
-    maintainers = with maintainers; [ peterhoeg ];
+    maintainers = with maintainers; [ pennae peterhoeg ];
   };
 
   nodes = let
@@ -17,77 +64,145 @@ in {
     };
   in {
     server = { pkgs, ... }: {
-      networking.firewall.allowedTCPPorts = [ port ];
+      networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ];
       services.mosquitto = {
-        inherit port;
         enable = true;
-        host = "0.0.0.0";
-        checkPasswords = true;
-        users.${username} = {
-          inherit password;
-          acl = [
-            "topic readwrite ${topic}"
-          ];
+        settings = {
+          sys_interval = 1;
         };
+        listeners = [
+          {
+            inherit port;
+            users = {
+              password_store = {
+                inherit password;
+              };
+              password_file = {
+                passwordFile = pkgs.writeText "mqtt-password" password;
+              };
+              hashed_store = {
+                inherit hashedPassword;
+              };
+              hashed_file = {
+                hashedPasswordFile = pkgs.writeText "mqtt-hashed-password" hashedPassword;
+              };
+
+              reader = {
+                inherit password;
+                acl = [
+                  "read ${topic}"
+                  "read $SYS/#" # so we always have something to read
+                ];
+              };
+              writer = {
+                inherit password;
+                acl = [ "write ${topic}" ];
+              };
+            };
+          }
+          {
+            port = tlsPort;
+            users.client1 = {
+              acl = [ "read $SYS/#" ];
+            };
+            settings = {
+              cafile = "${snakeOil}/ca.crt";
+              certfile = "${snakeOil}/server.crt";
+              keyfile = "${snakeOil}/server.key";
+              require_certificate = true;
+              use_identity_as_username = true;
+            };
+          }
+          {
+            port = anonPort;
+            omitPasswordAuth = true;
+            settings.allow_anonymous = true;
+            acl = [ "pattern read #" ];
+            users = {
+              anonWriter = {
+                password = "<ignored>" + password;
+                acl = [ "write ${topic}" ];
+              };
+            };
+          }
+        ];
       };
-
-      # disable private /tmp for this test
-      systemd.services.mosquitto.serviceConfig.PrivateTmp = lib.mkForce false;
     };
 
     client1 = client;
     client2 = client;
   };
 
-  testScript = let
-    file = "/tmp/msg";
-  in ''
-    def mosquitto_cmd(binary):
+  testScript = ''
+    def mosquitto_cmd(binary, user, topic, port):
         return (
-            "${pkgs.mosquitto}/bin/mosquitto_{} "
+            "mosquitto_{} "
             "-V mqttv311 "
             "-h server "
-            "-p ${toString port} "
-            "-u ${username} "
+            "-p {} "
+            "-u {} "
             "-P '${password}' "
-            "-t ${topic}"
-        ).format(binary)
+            "-t '{}'"
+        ).format(binary, port, user, topic)
 
 
-    def publish(args):
-        return "{} {}".format(mosquitto_cmd("pub"), args)
+    def publish(args, user, topic="${topic}", port=${toString port}):
+        return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args)
 
+    def subscribe(args, user, topic="${topic}", port=${toString port}):
+        return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args)
 
-    def subscribe(args):
-        return "({} -C 1 {} | tee ${file} &)".format(mosquitto_cmd("sub"), args)
+    def parallel(*fns):
+        from threading import Thread
+        threads = [ Thread(target=fn) for fn in fns ]
+        for t in threads: t.start()
+        for t in threads: t.join()
 
 
     start_all()
     server.wait_for_unit("mosquitto.service")
 
-    for machine in server, client1, client2:
-        machine.fail("test -f ${file}")
-
-    # QoS = 0, so only one subscribers should get it
-    server.execute(subscribe("-q 0"))
-
-    # we need to give the subscribers some time to connect
-    client2.execute("sleep 5")
-    client2.succeed(publish("-m FOO -q 0"))
-
-    server.wait_until_succeeds("grep -q FOO ${file}")
-    server.execute("rm ${file}")
-
-    # QoS = 1, so both subscribers should get it
-    server.execute(subscribe("-q 1"))
-    client1.execute(subscribe("-q 1"))
-
-    # we need to give the subscribers some time to connect
-    client2.execute("sleep 5")
-    client2.succeed(publish("-m BAR -q 1"))
-
-    for machine in server, client1:
-        machine.wait_until_succeeds("grep -q BAR ${file}")
-        machine.execute("rm ${file}")
+    with subtest("check passwords"):
+        client1.succeed(publish("-m test", "password_store"))
+        client1.succeed(publish("-m test", "password_file"))
+        client1.succeed(publish("-m test", "hashed_store"))
+        client1.succeed(publish("-m test", "hashed_file"))
+
+    with subtest("check acl"):
+        client1.succeed(subscribe("", "reader", topic="$SYS/#"))
+        client1.fail(subscribe("", "writer", topic="$SYS/#"))
+
+        parallel(
+            lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")),
+            lambda: [
+                server.wait_for_console_text("3688cdd7-aa07-42a4-be22-cb9352917e40"),
+                client2.succeed(publish("-m test", "writer"))
+            ])
+
+        parallel(
+            lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")),
+            lambda: [
+                server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"),
+                client2.succeed(publish("-m test", "reader"))
+            ])
+
+    with subtest("check tls"):
+        client1.succeed(
+            subscribe(
+                "--cafile ${snakeOil}/ca.crt "
+                "--cert ${snakeOil}/client1.crt "
+                "--key ${snakeOil}/client1.key",
+                topic="$SYS/#",
+                port=${toString tlsPort},
+                user="no_such_user"))
+
+    with subtest("check omitPasswordAuth"):
+        parallel(
+            lambda: client1.succeed(subscribe("-i fd56032c-d9cb-4813-a3b4-6be0e04c8fc3",
+                "anonReader", port=${toString anonPort})),
+            lambda: [
+                server.wait_for_console_text("fd56032c-d9cb-4813-a3b4-6be0e04c8fc3"),
+                client2.succeed(publish("-m test", "anonWriter", port=${toString anonPort}))
+            ])
   '';
 })
diff --git a/nixos/tests/mpv.nix b/nixos/tests/mpv.nix
index bcfc17cf33286..a4803f3cb5b55 100644
--- a/nixos/tests/mpv.nix
+++ b/nixos/tests/mpv.nix
@@ -14,14 +14,14 @@ in
     {
       environment.systemPackages = [
         pkgs.curl
-        (pkgs.mpv-with-scripts.override {
+        (pkgs.wrapMpv pkgs.mpv-unwrapped {
           scripts = [ pkgs.mpvScripts.simple-mpv-webui ];
         })
       ];
     };
 
   testScript = ''
-    machine.execute("set -m; mpv --script-opts=webui-port=${port} --idle=yes &")
+    machine.execute("set -m; mpv --script-opts=webui-port=${port} --idle=yes >&2 &")
     machine.wait_for_open_port(${port})
     assert "<title>simple-mpv-webui" in machine.succeed("curl -s localhost:${port}")
   '';
diff --git a/nixos/tests/mumble.nix b/nixos/tests/mumble.nix
index 717f3c7892888..2b5cc20163bcb 100644
--- a/nixos/tests/mumble.nix
+++ b/nixos/tests/mumble.nix
@@ -38,8 +38,8 @@ in
     client1.wait_for_x()
     client2.wait_for_x()
 
-    client1.execute("mumble mumble://client1:testpassword\@server/test &")
-    client2.execute("mumble mumble://client2:testpassword\@server/test &")
+    client1.execute("mumble mumble://client1:testpassword\@server/test >&2 &")
+    client2.execute("mumble mumble://client2:testpassword\@server/test >&2 &")
 
     # cancel client audio configuration
     client1.wait_for_window(r"Audio Tuning Wizard")
diff --git a/nixos/tests/musescore.nix b/nixos/tests/musescore.nix
index 96481a9a8bf48..7fd80d70df124 100644
--- a/nixos/tests/musescore.nix
+++ b/nixos/tests/musescore.nix
@@ -44,7 +44,7 @@ in
     )
 
     # Start MuseScore window
-    machine.execute("DISPLAY=:0.0 mscore &")
+    machine.execute("DISPLAY=:0.0 mscore >&2 &")
 
     # Wait until MuseScore has launched
     machine.wait_for_window("MuseScore")
diff --git a/nixos/tests/mysql/mariadb-galera-mariabackup.nix b/nixos/tests/mysql/mariadb-galera-mariabackup.nix
index 1c73bc854a57b..10682c361d1d7 100644
--- a/nixos/tests/mysql/mariadb-galera-mariabackup.nix
+++ b/nixos/tests/mysql/mariadb-galera-mariabackup.nix
@@ -4,6 +4,16 @@ let
   mysqlenv-common      = pkgs.buildEnv { name = "mysql-path-env-common";      pathsToLink = [ "/bin" ]; paths = with pkgs; [ bash gawk gnutar inetutils which ]; };
   mysqlenv-mariabackup = pkgs.buildEnv { name = "mysql-path-env-mariabackup"; pathsToLink = [ "/bin" ]; paths = with pkgs; [ gzip iproute2 netcat procps pv socat ]; };
 
+  # Common user configuration
+  users = { ... }:
+  {
+    users.users.testuser = {
+      isSystemUser = true;
+      group = "testusers";
+    };
+    users.groups.testusers = { };
+  };
+
 in {
   name = "mariadb-galera-mariabackup";
   meta = with pkgs.lib.maintainers; {
@@ -17,6 +27,7 @@ in {
     galera_01 =
       { pkgs, ... }:
       {
+      imports = [ users ];
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -31,7 +42,6 @@ in {
         firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
         firewall.allowedUDPPorts = [ 4567 ];
       };
-      users.users.testuser = { isSystemUser = true; };
       systemd.services.mysql = with pkgs; {
         path = [ mysqlenv-common mysqlenv-mariabackup ];
       };
@@ -75,6 +85,7 @@ in {
     galera_02 =
       { pkgs, ... }:
       {
+      imports = [ users ];
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -89,7 +100,6 @@ in {
         firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
         firewall.allowedUDPPorts = [ 4567 ];
       };
-      users.users.testuser = { isSystemUser = true; };
       systemd.services.mysql = with pkgs; {
         path = [ mysqlenv-common mysqlenv-mariabackup ];
       };
@@ -122,6 +132,7 @@ in {
     galera_03 =
       { pkgs, ... }:
       {
+      imports = [ users ];
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -136,7 +147,6 @@ in {
         firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
         firewall.allowedUDPPorts = [ 4567 ];
       };
-      users.users.testuser = { isSystemUser = true; };
       systemd.services.mysql = with pkgs; {
         path = [ mysqlenv-common mysqlenv-mariabackup ];
       };
diff --git a/nixos/tests/mysql/mariadb-galera-rsync.nix b/nixos/tests/mysql/mariadb-galera-rsync.nix
index 709a8b5085cb0..701e01e887181 100644
--- a/nixos/tests/mysql/mariadb-galera-rsync.nix
+++ b/nixos/tests/mysql/mariadb-galera-rsync.nix
@@ -4,6 +4,16 @@ let
   mysqlenv-common      = pkgs.buildEnv { name = "mysql-path-env-common";      pathsToLink = [ "/bin" ]; paths = with pkgs; [ bash gawk gnutar inetutils which ]; };
   mysqlenv-rsync       = pkgs.buildEnv { name = "mysql-path-env-rsync";       pathsToLink = [ "/bin" ]; paths = with pkgs; [ lsof procps rsync stunnel ]; };
 
+  # Common user configuration
+  users = { ... }:
+  {
+    users.users.testuser = {
+      isSystemUser = true;
+      group = "testusers";
+    };
+    users.groups.testusers = { };
+  };
+
 in {
   name = "mariadb-galera-rsync";
   meta = with pkgs.lib.maintainers; {
@@ -17,6 +27,7 @@ in {
     galera_04 =
       { pkgs, ... }:
       {
+      imports = [ users ];
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -31,7 +42,6 @@ in {
         firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
         firewall.allowedUDPPorts = [ 4567 ];
       };
-      users.users.testuser = { isSystemUser = true; };
       systemd.services.mysql = with pkgs; {
         path = [ mysqlenv-common mysqlenv-rsync ];
       };
@@ -70,6 +80,7 @@ in {
     galera_05 =
       { pkgs, ... }:
       {
+      imports = [ users ];
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -84,7 +95,6 @@ in {
         firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
         firewall.allowedUDPPorts = [ 4567 ];
       };
-      users.users.testuser = { isSystemUser = true; };
       systemd.services.mysql = with pkgs; {
         path = [ mysqlenv-common mysqlenv-rsync ];
       };
@@ -116,6 +126,7 @@ in {
     galera_06 =
       { pkgs, ... }:
       {
+      imports = [ users ];
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -130,7 +141,6 @@ in {
         firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
         firewall.allowedUDPPorts = [ 4567 ];
       };
-      users.users.testuser = { isSystemUser = true; };
       systemd.services.mysql = with pkgs; {
         path = [ mysqlenv-common mysqlenv-rsync ];
       };
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index 2ec9c3d50a3c5..2ac2b34a18e2b 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -1,4 +1,26 @@
-import ./../make-test-python.nix ({ pkgs, ...} : {
+import ./../make-test-python.nix ({ pkgs, ...}:
+
+
+let
+  # Setup common users
+  users = { ... }:
+  {
+    users.groups.testusers = { };
+
+    users.users.testuser = {
+      isSystemUser = true;
+      group = "testusers";
+    };
+
+    users.users.testuser2 = {
+      isSystemUser = true;
+      group = "testusers";
+    };
+  };
+
+in
+
+{
   name = "mysql";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ eelco shlevy ];
@@ -9,8 +31,8 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
       { pkgs, ... }:
 
       {
-        users.users.testuser = { isSystemUser = true; };
-        users.users.testuser2 = { isSystemUser = true; };
+        imports = [ users ];
+
         services.mysql.enable = true;
         services.mysql.initialDatabases = [
           { name = "testdb3"; schema = ./testdb.sql; }
@@ -40,12 +62,8 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
       { pkgs, ... }:
 
       {
-        # prevent oom:
-        # Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
-        virtualisation.memorySize = 1024;
+        imports = [ users ];
 
-        users.users.testuser = { isSystemUser = true; };
-        users.users.testuser2 = { isSystemUser = true; };
         services.mysql.enable = true;
         services.mysql.initialDatabases = [
           { name = "testdb3"; schema = ./testdb.sql; }
@@ -75,8 +93,8 @@ import ./../make-test-python.nix ({ pkgs, ...} : {
       { pkgs, ... }:
 
       {
-        users.users.testuser = { isSystemUser = true; };
-        users.users.testuser2 = { isSystemUser = true; };
+        imports = [ users ];
+
         services.mysql.enable = true;
         services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" ''
           ALTER USER root@localhost IDENTIFIED WITH unix_socket;
diff --git a/nixos/tests/networking-proxy.nix b/nixos/tests/networking-proxy.nix
index 62b5e690f6d1e..fcb2558cf3b08 100644
--- a/nixos/tests/networking-proxy.nix
+++ b/nixos/tests/networking-proxy.nix
@@ -8,7 +8,6 @@ let default-config = {
 
         services.xserver.enable = false;
 
-        virtualisation.memorySize = 128;
       };
 in import ./make-test-python.nix ({ pkgs, ...} : {
   name = "networking-proxy";
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 22f7ca5a9b825..647c8942b37d9 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -8,7 +8,7 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
-  qemu-flags = import ../lib/qemu-flags.nix { inherit pkgs; };
+  qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
 
   router = { config, pkgs, lib, ... }:
     with pkgs.lib;
@@ -42,7 +42,7 @@ let
         machines = flip map vlanIfs (vlan:
           {
             hostName = "client${toString vlan}";
-            ethernetAddress = qemu-flags.qemuNicMac vlan 1;
+            ethernetAddress = qemu-common.qemuNicMac vlan 1;
             ipAddress = "192.168.${toString vlan}.2";
           }
         );
@@ -380,12 +380,57 @@ let
               router.wait_until_succeeds("ping -c 1 192.168.1.3")
         '';
     };
+    fou = {
+      name = "foo-over-udp";
+      nodes.machine = { ... }: {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.eth1.ipv4.addresses = mkOverride 0
+            [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          fooOverUDP = {
+            fou1 = { port = 9001; };
+            fou2 = { port = 9002; protocol = 41; };
+            fou3 = mkIf (!networkd)
+              { port = 9003; local.address = "192.168.1.1"; };
+            fou4 = mkIf (!networkd)
+              { port = 9004; local = { address = "192.168.1.1"; dev = "eth1"; }; };
+          };
+        };
+        systemd.services = {
+          fou3-fou-encap.after = optional (!networkd) "network-addresses-eth1.service";
+        };
+      };
+      testScript = { ... }:
+        ''
+          import json
+
+          machine.wait_for_unit("network.target")
+          fous = json.loads(machine.succeed("ip -json fou show"))
+          assert {"port": 9001, "gue": None, "family": "inet"} in fous, "fou1 exists"
+          assert {"port": 9002, "ipproto": 41, "family": "inet"} in fous, "fou2 exists"
+        '' + optionalString (!networkd) ''
+          assert {
+              "port": 9003,
+              "gue": None,
+              "family": "inet",
+              "local": "192.168.1.1",
+          } in fous, "fou3 exists"
+          assert {
+              "port": 9004,
+              "gue": None,
+              "family": "inet",
+              "local": "192.168.1.1",
+              "dev": "eth1",
+          } in fous, "fou4 exists"
+        '';
+    };
     sit = let
       node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
         virtualisation.vlans = [ 1 ];
         networking = {
           useNetworkd = networkd;
-          firewall.enable = false;
           useDHCP = false;
           sits.sit = {
             inherit remote;
@@ -400,8 +445,30 @@ let
       };
     in {
       name = "Sit";
-      nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
-      nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
+      # note on firewalling: the two nodes are explicitly asymmetric.
+      # client1 sends SIT packets in UDP, but accepts only proto-41 incoming.
+      # client2 does the reverse, sending in proto-41 and accepting only UDP incoming.
+      # that way we'll notice when either SIT itself or FOU breaks.
+      nodes.client1 = args@{ pkgs, ... }:
+        mkMerge [
+          (node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; } args)
+          {
+            networking = {
+              firewall.extraCommands = "iptables -A INPUT -p 41 -j ACCEPT";
+              sits.sit.encapsulation = { type = "fou"; port = 9001; };
+            };
+          }
+        ];
+      nodes.client2 = args@{ pkgs, ... }:
+        mkMerge [
+          (node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; } args)
+          {
+            networking = {
+              firewall.allowedUDPPorts = [ 9001 ];
+              fooOverUDP.fou1 = { port = 9001; protocol = 41; };
+            };
+          }
+        ];
       testScript = { ... }:
         ''
           start_all()
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index c4ce34748ace7..eb37470a4c7bb 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -1,4 +1,6 @@
-import ../make-test-python.nix ({ pkgs, ...}: let
+args@{ pkgs, nextcloudVersion ? 22, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
   adminpass = "notproduction";
   adminuser = "root";
 in {
@@ -31,14 +33,20 @@ in {
     in {
       networking.firewall.allowedTCPPorts = [ 80 ];
 
+      systemd.tmpfiles.rules = [
+        "d /var/lib/nextcloud-data 0750 nextcloud nginx - -"
+      ];
+
       services.nextcloud = {
         enable = true;
+        datadir = "/var/lib/nextcloud-data";
         hostName = "nextcloud";
         config = {
           # Don't inherit adminuser since "root" is supposed to be the default
-          inherit adminpass;
+          adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
           dbtableprefix = "nixos_";
         };
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
         autoUpdateApps = {
           enable = true;
           startAt = "20:00";
@@ -95,9 +103,10 @@ in {
         "${withRcloneEnv} ${copySharedFile}"
     )
     client.wait_for_unit("multi-user.target")
+    nextcloud.succeed("test -f /var/lib/nextcloud-data/data/root/files/test-shared-file")
     client.succeed(
         "${withRcloneEnv} ${diffSharedFile}"
     )
     assert "hi" in client.succeed("cat /mnt/dav/test-shared-file")
   '';
-})
+})) args
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index e4c7a70606cf8..bd7a7aacdc91a 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/nixos/tests/nextcloud/default.nix
@@ -2,8 +2,20 @@
   config ? {},
   pkgs ? import ../../.. { inherit system config; }
 }:
-{
-  basic = import ./basic.nix { inherit system pkgs; };
-  with-postgresql-and-redis = import ./with-postgresql-and-redis.nix { inherit system pkgs; };
-  with-mysql-and-memcached = import ./with-mysql-and-memcached.nix { inherit system pkgs; };
-}
+
+with pkgs.lib;
+
+foldl
+  (matrix: ver: matrix // {
+    "basic${toString ver}" = import ./basic.nix { inherit system pkgs; nextcloudVersion = ver; };
+    "with-postgresql-and-redis${toString ver}" = import ./with-postgresql-and-redis.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
+    "with-mysql-and-memcached${toString ver}" = import ./with-mysql-and-memcached.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
+  })
+  {}
+  [ 21 22 ]
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
index 82041874de43f..80cb63df5dbe9 100644
--- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -1,4 +1,6 @@
-import ../make-test-python.nix ({ pkgs, ...}: let
+args@{ pkgs, nextcloudVersion ? 22, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
   adminpass = "hunter2";
   adminuser = "root";
 in {
@@ -18,6 +20,7 @@ in {
         enable = true;
         hostName = "nextcloud";
         https = true;
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
         caching = {
           apcu = true;
           redis = false;
@@ -29,9 +32,9 @@ in {
           dbuser = "nextcloud";
           dbhost = "127.0.0.1";
           dbport = 3306;
-          dbpass = "hunter2";
+          dbpassFile = "${pkgs.writeText "dbpass" "hunter2" }";
           # Don't inherit adminuser since "root" is supposed to be the default
-          inherit adminpass;
+          adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
         };
       };
 
@@ -39,6 +42,13 @@ in {
         enable = true;
         bind = "127.0.0.1";
         package = pkgs.mariadb;
+
+        # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6,
+        # this is a workaround.
+        # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22
+        extraOptions = ''
+          innodb_read_only_compressed=0
+        '';
         initialScript = pkgs.writeText "mysql-init" ''
           CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'hunter2';
           CREATE DATABASE IF NOT EXISTS nextcloud;
@@ -96,4 +106,4 @@ in {
         "${withRcloneEnv} ${diffSharedFile}"
     )
   '';
-})
+})) args
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 81af620598ee8..36a69fda505ba 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -1,4 +1,6 @@
-import ../make-test-python.nix ({ pkgs, ...}: let
+args@{ pkgs, nextcloudVersion ? 22, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
   adminpass = "hunter2";
   adminuser = "custom-admin-username";
 in {
@@ -17,6 +19,7 @@ in {
       services.nextcloud = {
         enable = true;
         hostName = "nextcloud";
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
         caching = {
           apcu = false;
           redis = true;
@@ -96,4 +99,4 @@ in {
         "${withRcloneEnv} ${diffSharedFile}"
     )
   '';
-})
+})) args
diff --git a/nixos/tests/nfs/simple.nix b/nixos/tests/nfs/simple.nix
index 6a01089c0828c..1e319a8eec810 100644
--- a/nixos/tests/nfs/simple.nix
+++ b/nixos/tests/nfs/simple.nix
@@ -66,7 +66,7 @@ in
           client2.succeed("time flock -n -s /data/lock true")
 
       with subtest("client 2 fails to acquire lock held by client 1"):
-          client1.succeed("flock -x /data/lock -c 'touch locked; sleep 100000' &")
+          client1.succeed("flock -x /data/lock -c 'touch locked; sleep 100000' >&2 &")
           client1.wait_for_file("locked")
           client2.fail("flock -n -s /data/lock true")
 
diff --git a/nixos/tests/nginx-etag.nix b/nixos/tests/nginx-etag.nix
index 63ab2e0c6c275..b69511d081d4b 100644
--- a/nixos/tests/nginx-etag.nix
+++ b/nixos/tests/nginx-etag.nix
@@ -37,7 +37,6 @@ import ./make-test-python.nix {
     };
 
     client = { pkgs, lib, ... }: {
-      virtualisation.memorySize = 512;
       environment.systemPackages = let
         testRunner = pkgs.writers.writePython3Bin "test-runner" {
           libraries = [ pkgs.python3Packages.selenium ];
@@ -76,7 +75,7 @@ import ./make-test-python.nix {
 
     server.wait_for_unit("nginx.service")
     client.wait_for_unit("multi-user.target")
-    client.execute("test-runner &")
+    client.execute("test-runner >&2 &")
     client.wait_for_file("/tmp/passed_stage1")
 
     server.succeed(
diff --git a/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix
new file mode 100644
index 0000000000000..ec3d028aabae8
--- /dev/null
+++ b/nixos/tests/nixops/default.nix
@@ -0,0 +1,114 @@
+{ pkgs, ... }:
+let
+  inherit (pkgs) lib;
+
+  tests = {
+    # TODO: uncomment stable
+    #  - Blocked on https://github.com/NixOS/nixpkgs/issues/138584 which has a
+    #    PR in staging: https://github.com/NixOS/nixpkgs/pull/139986
+    #  - Alternatively, blocked on a NixOps 2 release
+    #    https://github.com/NixOS/nixops/issues/1242
+    # stable = testsLegacyNetwork { nixopsPkg = pkgs.nixops; };
+    unstable = testsForPackage { nixopsPkg = pkgs.nixopsUnstable; };
+
+    # inherit testsForPackage;
+  };
+
+  testsForPackage = lib.makeOverridable (args: lib.recurseIntoAttrs {
+    legacyNetwork = testLegacyNetwork args;
+  });
+
+  testLegacyNetwork = { nixopsPkg }: pkgs.nixosTest ({
+    nodes = {
+      deployer = { config, lib, nodes, pkgs, ... }: {
+        imports = [ ../../modules/installer/cd-dvd/channel.nix ];
+        environment.systemPackages = [ nixopsPkg ];
+        nix.binaryCaches = lib.mkForce [ ];
+        users.users.person.isNormalUser = true;
+        virtualisation.writableStore = true;
+        virtualisation.additionalPaths = [
+          pkgs.hello
+          pkgs.figlet
+
+          # This includes build dependencies all the way down. Not efficient,
+          # but we do need build deps to an *arbitrary* depth, which is hard to
+          # determine.
+          (allDrvOutputs nodes.server.config.system.build.toplevel)
+        ];
+      };
+      server = { lib, ... }: {
+        imports = [ ./legacy/base-configuration.nix ];
+      };
+    };
+
+    testScript = { nodes }:
+      let
+        deployerSetup = pkgs.writeScript "deployerSetup" ''
+          #!${pkgs.runtimeShell}
+          set -eux -o pipefail
+          cp --no-preserve=mode -r ${./legacy} unicorn
+          cp --no-preserve=mode ${../ssh-keys.nix} unicorn/ssh-keys.nix
+          mkdir -p ~/.ssh
+          cp ${snakeOilPrivateKey} ~/.ssh/id_ed25519
+          chmod 0400 ~/.ssh/id_ed25519
+        '';
+        serverNetworkJSON = pkgs.writeText "server-network.json"
+          (builtins.toJSON nodes.server.config.system.build.networkConfig);
+      in
+      ''
+        import shlex
+
+        def deployer_do(cmd):
+            cmd = shlex.quote(cmd)
+            return deployer.succeed(f"su person -l -c {cmd} &>/dev/console")
+
+        start_all()
+
+        deployer_do("cat /etc/hosts")
+
+        deployer_do("${deployerSetup}")
+        deployer_do("cp ${serverNetworkJSON} unicorn/server-network.json")
+
+        # Establish that ssh works, regardless of nixops
+        # Easy way to accept the server host key too.
+        server.wait_for_open_port(22)
+        deployer.wait_for_unit("network.target")
+
+        # Put newlines on console, to flush the console reader's line buffer
+        # in case nixops' last output did not end in a newline, as is the case
+        # with a status line (if implemented?)
+        deployer.succeed("while sleep 60s; do echo [60s passed]; done >&2 &")
+
+        deployer_do("cd ~/unicorn; ssh -oStrictHostKeyChecking=accept-new root@server echo hi")
+
+        # Create and deploy
+        deployer_do("cd ~/unicorn; nixops create")
+
+        deployer_do("cd ~/unicorn; nixops deploy --confirm")
+
+        deployer_do("cd ~/unicorn; nixops ssh server 'hello | figlet'")
+      '';
+  });
+
+  inherit (import ../ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  /*
+    Return a store path with a closure containing everything including
+    derivations and all build dependency outputs, all the way down.
+  */
+  allDrvOutputs = pkg:
+    let name = lib.strings.sanitizeDerivationName "allDrvOutputs-${pkg.pname or pkg.name or "unknown"}";
+    in
+    pkgs.runCommand name { refs = pkgs.writeReferencesToFile pkg.drvPath; } ''
+      touch $out
+      while read ref; do
+        case $ref in
+          *.drv)
+            cat $ref >>$out
+            ;;
+        esac
+      done <$refs
+    '';
+
+in
+tests
diff --git a/nixos/tests/nixops/legacy/base-configuration.nix b/nixos/tests/nixops/legacy/base-configuration.nix
new file mode 100644
index 0000000000000..dba960f595c27
--- /dev/null
+++ b/nixos/tests/nixops/legacy/base-configuration.nix
@@ -0,0 +1,31 @@
+{ lib, modulesPath, pkgs, ... }:
+let
+  ssh-keys =
+    if builtins.pathExists ../../ssh-keys.nix
+    then # Outside sandbox
+      ../../ssh-keys.nix
+    else # In sandbox
+      ./ssh-keys.nix;
+
+  inherit (import ssh-keys pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in
+{
+  imports = [
+    (modulesPath + "/virtualisation/qemu-vm.nix")
+    (modulesPath + "/testing/test-instrumentation.nix")
+  ];
+  virtualisation.writableStore = true;
+  nix.binaryCaches = lib.mkForce [ ];
+  virtualisation.graphics = false;
+  documentation.enable = false;
+  services.qemuGuest.enable = true;
+  boot.loader.grub.enable = false;
+
+  services.openssh.enable = true;
+  users.users.root.openssh.authorizedKeys.keys = [
+    snakeOilPublicKey
+  ];
+  security.pam.services.sshd.limits =
+    [{ domain = "*"; item = "memlock"; type = "-"; value = 1024; }];
+}
diff --git a/nixos/tests/nixops/legacy/nixops.nix b/nixos/tests/nixops/legacy/nixops.nix
new file mode 100644
index 0000000000000..795dc2a718254
--- /dev/null
+++ b/nixos/tests/nixops/legacy/nixops.nix
@@ -0,0 +1,15 @@
+{
+  network = {
+    description = "Legacy Network using <nixpkgs> and legacy state.";
+    # NB this is not really what makes it a legacy network; lack of flakes is.
+    storage.legacy = { };
+  };
+  server = { lib, pkgs, ... }: {
+    deployment.targetEnv = "none";
+    imports = [
+      ./base-configuration.nix
+      (lib.modules.importJSON ./server-network.json)
+    ];
+    environment.systemPackages = [ pkgs.hello pkgs.figlet ];
+  };
+}
diff --git a/nixos/tests/odoo.nix b/nixos/tests/odoo.nix
new file mode 100644
index 0000000000000..96e3405482b41
--- /dev/null
+++ b/nixos/tests/odoo.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
+  name = "odoo";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mkg20001 ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+      };
+
+      services.odoo = {
+        enable = true;
+        domain = "localhost";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+  ''
+    server.wait_for_unit("odoo.service")
+    server.wait_until_succeeds("curl -s http://localhost:8069/web/database/selector | grep '<title>Odoo</title>'")
+    server.succeed("curl -s http://localhost/web/database/selector | grep '<title>Odoo</title>'")
+  '';
+})
diff --git a/nixos/tests/openarena.nix b/nixos/tests/openarena.nix
index 461a35e89fe79..63dc1b9a68570 100644
--- a/nixos/tests/openarena.nix
+++ b/nixos/tests/openarena.nix
@@ -38,8 +38,8 @@ in {
       client1.wait_for_x()
       client2.wait_for_x()
 
-      client1.execute("openarena +set r_fullscreen 0 +set name Foo +connect server &")
-      client2.execute("openarena +set r_fullscreen 0 +set name Bar +connect server &")
+      client1.execute("openarena +set r_fullscreen 0 +set name Foo +connect server >&2 &")
+      client2.execute("openarena +set r_fullscreen 0 +set name Bar +connect server >&2 &")
 
       server.wait_until_succeeds(
           "journalctl -u openarena -e | grep -q 'Foo.*entered the game'"
diff --git a/nixos/tests/openresty-lua.nix b/nixos/tests/openresty-lua.nix
new file mode 100644
index 0000000000000..b177b3c194d78
--- /dev/null
+++ b/nixos/tests/openresty-lua.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    lualibs = [
+      pkgs.lua.pkgs.markdown
+    ];
+
+    getPath = lib: type: "${lib}/share/lua/${pkgs.lua.luaversion}/?.${type}";
+    getLuaPath = lib: getPath lib "lua";
+    luaPath = lib.concatStringsSep ";" (map getLuaPath lualibs);
+  in
+  {
+    name = "openresty-lua";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bbigras ];
+    };
+
+    nodes = {
+      webserver = { pkgs, lib, ... }: {
+        services.nginx = {
+          enable = true;
+          package = pkgs.openresty;
+
+          commonHttpConfig = ''
+            lua_package_path '${luaPath};;';
+          '';
+
+          virtualHosts."default" = {
+            default = true;
+            locations."/" = {
+              extraConfig = ''
+                default_type text/html;
+                access_by_lua '
+                  local markdown = require "markdown"
+                  markdown("source")
+                ';
+              '';
+            };
+          };
+        };
+      };
+    };
+
+    testScript = { nodes, ... }:
+      ''
+        url = "http://localhost"
+
+        webserver.wait_for_unit("nginx")
+        webserver.wait_for_open_port(80)
+
+        http_code = webserver.succeed(
+          f"curl -w '%{{http_code}}' --head --fail {url}"
+        )
+        assert http_code.split("\n")[-1] == "200"
+      '';
+  })
diff --git a/nixos/tests/opensmtpd-rspamd.nix b/nixos/tests/opensmtpd-rspamd.nix
index 9cb2624e6c4e9..19969a7b47ddd 100644
--- a/nixos/tests/opensmtpd-rspamd.nix
+++ b/nixos/tests/opensmtpd-rspamd.nix
@@ -39,7 +39,6 @@ import ./make-test-python.nix {
 
     smtp2 = { pkgs, ... }: {
       imports = [ common/user-account.nix ];
-      virtualisation.memorySize = 512;
       networking = {
         firewall.allowedTCPPorts = [ 25 143 ];
         useDHCP = false;
diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix
index 3cc38ebe34716..a7b955d447215 100644
--- a/nixos/tests/os-prober.nix
+++ b/nixos/tests/os-prober.nix
@@ -58,9 +58,9 @@ let
           [ ./hardware-configuration.nix
             <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
           ];
-    } // (builtins.fromJSON (builtins.readFile ${
+    } // pkgs.lib.importJSON ${
       pkgs.writeText "simpleConfig.json" (builtins.toJSON simpleConfig)
-    })))
+    })
   '';
 in {
   name = "os-prober";
diff --git a/nixos/tests/owncast.nix b/nixos/tests/owncast.nix
new file mode 100644
index 0000000000000..debb34f5009dc
--- /dev/null
+++ b/nixos/tests/owncast.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "owncast";
+  meta = with pkgs.lib.maintainers; { maintainers = [ MayNiklas ]; };
+
+  nodes = {
+    client = { pkgs, ... }: with pkgs.lib; {
+      networking = {
+        dhcpcd.enable = false;
+        interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
+        interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+    server = { pkgs, ... }: with pkgs.lib; {
+      networking = {
+        dhcpcd.enable = false;
+        useNetworkd = true;
+        useDHCP = false;
+        interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
+        interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
+
+        firewall.allowedTCPPorts = [ 8080 ];
+      };
+
+      services.owncast = {
+        enable = true;
+        listen = "0.0.0.0";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    client.wait_for_unit("network-online.target")
+    server.wait_for_unit("network-online.target")
+    server.wait_for_unit("owncast.service")
+    server.wait_until_succeeds("ss -ntl | grep -q 8080")
+
+    client.succeed("curl http://192.168.1.1:8080/api/status")
+    client.succeed("curl http://[fd00::1]:8080/api/status")
+  '';
+})
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index 3894440333c99..989d29a966dfb 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -1,10 +1,10 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ...} :
 
 {
   name = "pantheon";
 
-  meta = with pkgs.lib.maintainers; {
-    maintainers = pkgs.pantheon.maintainers;
+  meta = with lib; {
+    maintainers = teams.pantheon.members;
   };
 
   machine = { ... }:
@@ -15,7 +15,6 @@ import ./make-test-python.nix ({ pkgs, ...} :
     services.xserver.enable = true;
     services.xserver.desktopManager.pantheon.enable = true;
 
-    virtualisation.memorySize = 1024;
   };
 
   enableOCR = true;
@@ -45,13 +44,13 @@ import ./make-test-python.nix ({ pkgs, ...} :
     with subtest("Check if pantheon session components actually start"):
         machine.wait_until_succeeds("pgrep gala")
         machine.wait_for_window("gala")
-        machine.wait_until_succeeds("pgrep wingpanel")
-        machine.wait_for_window("wingpanel")
+        machine.wait_until_succeeds("pgrep -f io.elementary.wingpanel")
+        machine.wait_for_window("io.elementary.wingpanel")
         machine.wait_until_succeeds("pgrep plank")
         machine.wait_for_window("plank")
 
     with subtest("Open elementary terminal"):
-        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.terminal &'")
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.terminal >&2 &'")
         machine.wait_for_window("io.elementary.terminal")
         machine.sleep(20)
         machine.screenshot("screen")
diff --git a/nixos/tests/paperless-ng.nix b/nixos/tests/paperless-ng.nix
index a4b2f348ec328..618eeec6b1259 100644
--- a/nixos/tests/paperless-ng.nix
+++ b/nixos/tests/paperless-ng.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ lib, ... }: {
       enable = true;
       passwordFile = builtins.toFile "password" "admin";
     };
-    virtualisation.memorySize = 1024;
   };
 
   testScript = ''
diff --git a/nixos/tests/parsedmarc/default.nix b/nixos/tests/parsedmarc/default.nix
new file mode 100644
index 0000000000000..d838d3b6a39c6
--- /dev/null
+++ b/nixos/tests/parsedmarc/default.nix
@@ -0,0 +1,224 @@
+# This tests parsedmarc by sending a report to its monitored email
+# address and reading the results out of Elasticsearch.
+
+{ pkgs, ... }@args:
+let
+  inherit (import ../../lib/testing-python.nix args) makeTest;
+
+  dmarcTestReport = builtins.fetchurl {
+    name = "dmarc-test-report";
+    url = "https://github.com/domainaware/parsedmarc/raw/f45ab94e0608088e0433557608d9f4e9517d3afe/samples/aggregate/estadocuenta1.infonacot.gob.mx!example.com!1536853302!1536939702!2940.xml.zip";
+    sha256 = "0dq64cj49711kbja27pjl2hy0d3azrjxg91kqrh40x46fkn1dwkx";
+  };
+
+  sendEmail = address:
+    pkgs.writeScriptBin "send-email" ''
+      #!${pkgs.python3.interpreter}
+      import smtplib
+      from email import encoders
+      from email.mime.base import MIMEBase
+      from email.mime.multipart import MIMEMultipart
+      from email.mime.text import MIMEText
+
+      sender_email = "dmarc_tester@fake.domain"
+      receiver_email = "${address}"
+
+      message = MIMEMultipart()
+      message["From"] = sender_email
+      message["To"] = receiver_email
+      message["Subject"] = "DMARC test"
+
+      message.attach(MIMEText("Testing parsedmarc", "plain"))
+
+      attachment = MIMEBase("application", "zip")
+
+      with open("${dmarcTestReport}", "rb") as report:
+          attachment.set_payload(report.read())
+
+      encoders.encode_base64(attachment)
+
+      attachment.add_header(
+          "Content-Disposition",
+          "attachment; filename= estadocuenta1.infonacot.gob.mx!example.com!1536853302!1536939702!2940.xml.zip",
+      )
+
+      message.attach(attachment)
+      text = message.as_string()
+
+      with smtplib.SMTP('localhost') as server:
+          server.sendmail(sender_email, receiver_email, text)
+          server.quit()
+    '';
+in
+{
+  localMail = makeTest
+    {
+      name = "parsedmarc-local-mail";
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ talyz ];
+      };
+
+      nodes.parsedmarc =
+        { nodes, ... }:
+        {
+          virtualisation.memorySize = 2048;
+
+          services.postfix = {
+            enableSubmission = true;
+            enableSubmissions = true;
+            submissionsOptions = {
+              smtpd_sasl_auth_enable = "yes";
+              smtpd_client_restrictions = "permit";
+            };
+          };
+
+          services.parsedmarc = {
+            enable = true;
+            provision = {
+              geoIp = false;
+              localMail = {
+                enable = true;
+                hostname = "localhost";
+              };
+            };
+          };
+
+          services.elasticsearch.package = pkgs.elasticsearch7-oss;
+
+          environment.systemPackages = [
+            (sendEmail "dmarc@localhost")
+            pkgs.jq
+          ];
+        };
+
+      testScript = { nodes }:
+        let
+          esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
+        in ''
+          parsedmarc.start()
+          parsedmarc.wait_for_unit("postfix.service")
+          parsedmarc.wait_for_unit("dovecot2.service")
+          parsedmarc.wait_for_unit("parsedmarc.service")
+          parsedmarc.wait_until_succeeds(
+              "curl -sS -f http://localhost:${esPort}"
+          )
+
+          parsedmarc.fail(
+              "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940 | jq -e 'if .hits.total.value > 0 then true else null end'"
+          )
+          parsedmarc.succeed("send-email")
+          parsedmarc.wait_until_succeeds(
+              "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940 | jq -e 'if .hits.total.value > 0 then true else null end'"
+          )
+        '';
+    };
+
+  externalMail =
+    let
+      certs = import ../common/acme/server/snakeoil-certs.nix;
+      mailDomain = certs.domain;
+      parsedmarcDomain = "parsedmarc.fake.domain";
+    in
+      makeTest {
+        name = "parsedmarc-external-mail";
+        meta = with pkgs.lib.maintainers; {
+          maintainers = [ talyz ];
+        };
+
+        nodes = {
+          parsedmarc =
+            { nodes, ... }:
+            {
+              virtualisation.memorySize = 2048;
+
+              security.pki.certificateFiles = [
+                certs.ca.cert
+              ];
+
+              networking.extraHosts = ''
+                127.0.0.1 ${parsedmarcDomain}
+                ${nodes.mail.config.networking.primaryIPAddress} ${mailDomain}
+              '';
+
+              services.parsedmarc = {
+                enable = true;
+                provision.geoIp = false;
+                settings.imap = {
+                  host = mailDomain;
+                  port = 993;
+                  ssl = true;
+                  user = "alice";
+                  password = "${pkgs.writeText "imap-password" "foobar"}";
+                  watch = true;
+                };
+              };
+
+              services.elasticsearch.package = pkgs.elasticsearch7-oss;
+
+              environment.systemPackages = [
+                pkgs.jq
+              ];
+            };
+
+          mail =
+            { nodes, ... }:
+            {
+              imports = [ ../common/user-account.nix ];
+
+              networking.extraHosts = ''
+                127.0.0.1 ${mailDomain}
+                ${nodes.parsedmarc.config.networking.primaryIPAddress} ${parsedmarcDomain}
+              '';
+
+              services.dovecot2 = {
+                enable = true;
+                protocols = [ "imap" ];
+                sslCACert = "${certs.ca.cert}";
+                sslServerCert = "${certs.${mailDomain}.cert}";
+                sslServerKey = "${certs.${mailDomain}.key}";
+              };
+
+              services.postfix = {
+                enable = true;
+                origin = mailDomain;
+                config = {
+                  myhostname = mailDomain;
+                  mydestination = mailDomain;
+                };
+                enableSubmission = true;
+                enableSubmissions = true;
+                submissionsOptions = {
+                  smtpd_sasl_auth_enable = "yes";
+                  smtpd_client_restrictions = "permit";
+                };
+              };
+              environment.systemPackages = [ (sendEmail "alice@${mailDomain}") ];
+
+              networking.firewall.allowedTCPPorts = [ 993 ];
+            };
+        };
+
+        testScript = { nodes }:
+          let
+            esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
+          in ''
+            mail.start()
+            mail.wait_for_unit("postfix.service")
+            mail.wait_for_unit("dovecot2.service")
+
+            parsedmarc.start()
+            parsedmarc.wait_for_unit("parsedmarc.service")
+            parsedmarc.wait_until_succeeds(
+                "curl -sS -f http://localhost:${esPort}"
+            )
+
+            parsedmarc.fail(
+                "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940 | jq -e 'if .hits.total.value > 0 then true else null end'"
+            )
+            mail.succeed("send-email")
+            parsedmarc.wait_until_succeeds(
+                "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940 | jq -e 'if .hits.total.value > 0 then true else null end'"
+            )
+          '';
+      };
+}
diff --git a/nixos/tests/pict-rs.nix b/nixos/tests/pict-rs.nix
new file mode 100644
index 0000000000000..432fd6a50ccd8
--- /dev/null
+++ b/nixos/tests/pict-rs.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  {
+    name = "pict-rs";
+    meta.maintainers = with lib.maintainers; [ happysalada ];
+
+    machine = { ... }: {
+      environment.systemPackages = with pkgs; [ curl jq ];
+      services.pict-rs.enable = true;
+    };
+
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("pict-rs")
+      machine.wait_for_open_port("8080")
+    '';
+  })
diff --git a/nixos/tests/plasma5-systemd-start.nix b/nixos/tests/plasma5-systemd-start.nix
new file mode 100644
index 0000000000000..72de19af70cef
--- /dev/null
+++ b/nixos/tests/plasma5-systemd-start.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma5-systemd-start";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ oxalica ];
+  };
+
+  machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver = {
+      enable = true;
+      displayManager.sddm.enable = true;
+      displayManager.defaultSession = "plasma";
+      desktopManager.plasma5.enable = true;
+      desktopManager.plasma5.runUsingSystemd = true;
+      displayManager.autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    with subtest("Wait for login"):
+        start_all()
+        machine.wait_for_file("${user.home}/.Xauthority")
+        machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+    with subtest("Check plasmashell started"):
+        machine.wait_until_succeeds("pgrep plasmashell")
+        machine.wait_for_window("^Desktop ")
+
+    status, result = machine.systemctl('--no-pager show plasma-plasmashell.service', user='alice')
+    assert status == 0, 'Service not found'
+    assert 'ActiveState=active' in result.split('\n'), 'Systemd service not active'
+  '';
+})
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index f09859a055d5c..5c7ea602f79e0 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -12,14 +12,13 @@ import ./make-test-python.nix ({ pkgs, ...} :
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
     services.xserver.displayManager.sddm.enable = true;
-    services.xserver.displayManager.defaultSession = "plasma5";
+    services.xserver.displayManager.defaultSession = "plasma";
     services.xserver.desktopManager.plasma5.enable = true;
     services.xserver.displayManager.autoLogin = {
       enable = true;
       user = "alice";
     };
     hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
-    virtualisation.memorySize = 1024;
   };
 
   testScript = { nodes, ... }: let
@@ -42,15 +41,15 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
 
     with subtest("Run Dolphin"):
-        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin &'")
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'")
         machine.wait_for_window(" Dolphin")
 
     with subtest("Run Konsole"):
-        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 konsole &'")
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 konsole >&2 &'")
         machine.wait_for_window("Konsole")
 
     with subtest("Run systemsettings"):
-        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 systemsettings5 &'")
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 systemsettings5 >&2 &'")
         machine.wait_for_window("Settings")
 
     with subtest("Wait to get a screenshot"):
diff --git a/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix
index d0ae1488d1346..bf3623fce38b7 100644
--- a/nixos/tests/pleroma.nix
+++ b/nixos/tests/pleroma.nix
@@ -202,7 +202,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
       security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
       networking.extraHosts = hosts nodes;
       networking.firewall.enable = false;
-      virtualisation.memorySize = 512;
       environment.systemPackages = with pkgs; [
         provision-db
         provision-secrets
diff --git a/nixos/tests/plotinus.nix b/nixos/tests/plotinus.nix
index ddd6a4c119461..af38b41813b7b 100644
--- a/nixos/tests/plotinus.nix
+++ b/nixos/tests/plotinus.nix
@@ -14,7 +14,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     machine.wait_for_x()
-    machine.succeed("gnome-calculator &")
+    machine.succeed("gnome-calculator >&2 &")
     machine.wait_for_window("gnome-calculator")
     machine.succeed(
         "xdotool search --sync --onlyvisible --class gnome-calculator "
diff --git a/nixos/tests/postfixadmin.nix b/nixos/tests/postfixadmin.nix
index aba5e3eed1021..b2712f4699aea 100644
--- a/nixos/tests/postfixadmin.nix
+++ b/nixos/tests/postfixadmin.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "postfixadmin";
-  meta = with pkgs.stdenv.lib.maintainers; {
+  meta = with pkgs.lib.maintainers; {
     maintainers = [ globin ];
   };
 
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index badcb99a57af3..6338fd8d8ac10 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -53,18 +53,10 @@ in {
 
     start_all()
 
-    with subtest("Make sure that cups is up on both sides"):
+    with subtest("Make sure that cups is up on both sides and printers are set up"):
         serviceServer.wait_for_unit("cups.service")
         serviceClient.wait_for_unit("cups.service")
-
-    with subtest(
-        "Wait until cups is fully initialized and ensure-printers has "
-        "executed with 10s delay"
-    ):
-        serviceClient.sleep(20)
-        socketActivatedClient.wait_until_succeeds(
-            "systemctl show ensure-printers | grep -q -E 'code=exited ; status=0'"
-        )
+        socketActivatedClient.wait_for_unit("ensure-printers.service")
 
 
     def test_printing(client, server):
diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix
index 4a94f07279469..c1141465ec24e 100644
--- a/nixos/tests/privacyidea.nix
+++ b/nixos/tests/privacyidea.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
 
   machine = { ... }: {
     virtualisation.cores = 2;
-    virtualisation.memorySize = 512;
 
     services.privacyidea = {
       enable = true;
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index c6e8fa5a9ee1c..d069854328a2c 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -280,6 +280,7 @@ let
       };
       exporterTest = ''
         wait_for_unit("prometheus-influxdb-exporter.service")
+        wait_for_open_port(9122)
         succeed(
           "curl -XPOST http://localhost:9122/write --data-binary 'influxdb_exporter,distro=nixos,added_in=21.09 value=1'"
         )
@@ -463,7 +464,6 @@ let
         extraFlags = [ "--lnd.network=regtest" ];
       };
       metricProvider = {
-        virtualisation.memorySize = 1024;
         systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15;
         systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ];
         services.bitcoind.regtest = {
@@ -554,7 +554,11 @@ let
             WorkingDirectory = "/var/spool/mail";
           };
         };
-        users.users.mailexporter.isSystemUser = true;
+        users.users.mailexporter = {
+          isSystemUser = true;
+          group = "mailexporter";
+        };
+        users.groups.mailexporter = {};
       };
       exporterTest = ''
         wait_for_unit("postfix.service")
@@ -948,7 +952,6 @@ let
       };
       metricProvider = {
         services.rspamd.enable = true;
-        virtualisation.memorySize = 1024;
       };
       exporterTest = ''
         wait_for_unit("rspamd.service")
diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix
index 70ac78a4a4689..a075cfc1f1b72 100644
--- a/nixos/tests/prometheus.nix
+++ b/nixos/tests/prometheus.nix
@@ -41,6 +41,7 @@ in import ./make-test-python.nix {
       networking.firewall.allowedTCPPorts = [ grpcPort ];
       services.prometheus = {
         enable = true;
+        enableReload = true;
         scrapeConfigs = [
           {
             job_name = "prometheus";
@@ -118,6 +119,32 @@ in import ./make-test-python.nix {
         #  };
         #};
       };
+      # Adds a "specialisation" of the above config which allows us to
+      # "switch" to it and see if the services.prometheus.enableReload
+      # functionality actually reloads the prometheus service instead of
+      # restarting it.
+      specialisation = {
+        "prometheus-config-change" = {
+          configuration = {
+            environment.systemPackages = [ pkgs.yq ];
+
+            # This configuration just adds a new prometheus job
+            # to scrape the node_exporter metrics of the s3 machine.
+            services.prometheus = {
+              scrapeConfigs = [
+                {
+                  job_name = "s3-node_exporter";
+                  static_configs = [
+                    {
+                      targets = [ "s3:9100" ];
+                    }
+                  ];
+                }
+              ];
+            };
+          };
+        };
+      };
     };
 
     query = { pkgs, ... }: {
@@ -161,7 +188,6 @@ in import ./make-test-python.nix {
       # Minio requires at least 1GiB of free disk space to run.
       virtualisation = {
         diskSize = 2 * 1024;
-        memorySize = 1024;
       };
       networking.firewall.allowedTCPPorts = [ minioPort ];
 
@@ -171,10 +197,17 @@ in import ./make-test-python.nix {
       };
 
       environment.systemPackages = [ pkgs.minio-client ];
+
+      services.prometheus.exporters.node = {
+        enable = true;
+        openFirewall = true;
+      };
     };
   };
 
   testScript = { nodes, ... } : ''
+    import json
+
     # Before starting the other machines we first make sure that our S3 service is online
     # and has a bucket added for thanos:
     s3.start()
@@ -193,6 +226,7 @@ in import ./make-test-python.nix {
 
     # Check if prometheus responds to requests:
     prometheus.wait_for_unit("prometheus.service")
+
     prometheus.wait_for_open_port(${toString queryPort})
     prometheus.succeed("curl -sf http://127.0.0.1:${toString queryPort}/metrics")
 
@@ -245,5 +279,61 @@ in import ./make-test-python.nix {
         + "jq .thanos.labels.some_label | "
         + "grep 'required by thanos'"
     )
+
+    # Check if switching to a NixOS configuration that changes the prometheus
+    # configuration reloads (instead of restarts) prometheus before the switch
+    # finishes successfully:
+    with subtest("config change reloads prometheus"):
+        # We check if prometheus has finished reloading by looking for the message
+        # "Completed loading of configuration file" in the journal between the start
+        # and finish of switching to the new NixOS configuration.
+        #
+        # To mark the start we record the journal cursor before starting the switch:
+        cursor_before_switching = json.loads(
+            prometheus.succeed("journalctl -n1 -o json --output-fields=__CURSOR")
+        )["__CURSOR"]
+
+        # Now we switch:
+        prometheus_config_change = prometheus.succeed(
+            "readlink /run/current-system/specialisation/prometheus-config-change"
+        ).strip()
+        prometheus.succeed(prometheus_config_change + "/bin/switch-to-configuration test")
+
+        # Next we retrieve all logs since the start of switching:
+        logs_after_starting_switching = prometheus.succeed(
+            """
+              journalctl --after-cursor='{cursor_before_switching}' -o json --output-fields=MESSAGE
+            """.format(
+                cursor_before_switching=cursor_before_switching
+            )
+        )
+
+        # Finally we check if the message "Completed loading of configuration file"
+        # occurs before the "finished switching to system configuration" message:
+        finished_switching_msg = (
+            "finished switching to system configuration " + prometheus_config_change
+        )
+        reloaded_before_switching_finished = False
+        finished_switching = False
+        for log_line in logs_after_starting_switching.split("\n"):
+            msg = json.loads(log_line)["MESSAGE"]
+            if "Completed loading of configuration file" in msg:
+                reloaded_before_switching_finished = True
+            if msg == finished_switching_msg:
+                finished_switching = True
+                break
+
+        assert reloaded_before_switching_finished
+        assert finished_switching
+
+        # Check if the reloaded config includes the new s3-node_exporter job:
+        prometheus.succeed(
+          """
+            curl -sf http://127.0.0.1:${toString queryPort}/api/v1/status/config \
+              | jq -r .data.yaml \
+              | yq '.scrape_configs | any(.job_name == "s3-node_exporter")' \
+              | grep true
+          """
+        )
   '';
 }
diff --git a/nixos/tests/prowlarr.nix b/nixos/tests/prowlarr.nix
new file mode 100644
index 0000000000000..4cbca107568f3
--- /dev/null
+++ b/nixos/tests/prowlarr.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "prowlarr";
+  meta.maintainers = with maintainers; [ jdreaver ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.prowlarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("prowlarr.service")
+    machine.wait_for_open_port("9696")
+    machine.succeed("curl --fail http://localhost:9696/")
+  '';
+})
diff --git a/nixos/tests/pt2-clone.nix b/nixos/tests/pt2-clone.nix
index 3c090b7de4286..364920c398711 100644
--- a/nixos/tests/pt2-clone.nix
+++ b/nixos/tests/pt2-clone.nix
@@ -22,7 +22,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       # Add a dummy sound card, or the program won't start
       machine.execute("modprobe snd-dummy")
 
-      machine.execute("pt2-clone &")
+      machine.execute("pt2-clone >&2 &")
 
       machine.wait_for_window(r"ProTracker")
       machine.sleep(5)
diff --git a/nixos/tests/rasdaemon.nix b/nixos/tests/rasdaemon.nix
new file mode 100644
index 0000000000000..e4bd8d96a8d53
--- /dev/null
+++ b/nixos/tests/rasdaemon.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ... } : {
+  name = "rasdaemon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ evils ];
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    hardware.rasdaemon = {
+      enable = true;
+      # should be enabled by default, just making sure
+      record = true;
+      # nonsense label
+      labels = ''
+        vendor: none
+          product: none
+          model: none
+            DIMM_0: 0.0.0;
+      '';
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      # confirm rasdaemon is running and has a valid database
+      # some disk errors detected in qemu for some reason ¯\_(ツ)_/¯
+      machine.succeed("ras-mc-ctl --errors | tee /dev/stderr | grep -q 'No .* errors.'")
+      # confirm the supplied labels text made it into the system
+      machine.succeed("grep -q 'vendor: none' /etc/ras/dimm_labels.d/labels >&2")
+      machine.shutdown()
+    '';
+})
diff --git a/nixos/tests/restart-by-activation-script.nix b/nixos/tests/restart-by-activation-script.nix
new file mode 100644
index 0000000000000..0eec292ea9e2b
--- /dev/null
+++ b/nixos/tests/restart-by-activation-script.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "restart-by-activation-script";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ das_j ];
+  };
+
+  machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    systemd.services.restart-me = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.coreutils}/bin/true";
+      };
+    };
+
+    systemd.services.reload-me = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = rec {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.coreutils}/bin/true";
+        ExecReload = ExecStart;
+      };
+    };
+
+    system.activationScripts.test = {
+      supportsDryActivation = true;
+      text = ''
+        if [ -e /test-the-activation-script ]; then
+          if [ "$NIXOS_ACTION" != dry-activate ]; then
+            touch /activation-was-run
+            echo restart-me.service > /run/nixos/activation-restart-list
+            echo reload-me.service > /run/nixos/activation-reload-list
+          else
+            echo restart-me.service > /run/nixos/dry-activation-restart-list
+            echo reload-me.service > /run/nixos/dry-activation-reload-list
+          fi
+        fi
+      '';
+    };
+  };
+
+  testScript = /* python */ ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("nothing happens when the activation script does nothing"):
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate 2>&1")
+        assert 'restart' not in out
+        assert 'reload' not in out
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration test")
+        assert 'restart' not in out
+        assert 'reload' not in out
+
+    machine.succeed("touch /test-the-activation-script")
+
+    with subtest("dry activation"):
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate 2>&1")
+        assert 'would restart the following units: restart-me.service' in out
+        assert 'would reload the following units: reload-me.service' in out
+        machine.fail("test -f /run/nixos/dry-activation-restart-list")
+        machine.fail("test -f /run/nixos/dry-activation-reload-list")
+
+    with subtest("real activation"):
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration test 2>&1")
+        assert 'restarting the following units: restart-me.service' in out
+        assert 'reloading the following units: reload-me.service' in out
+        machine.fail("test -f /run/nixos/activation-restart-list")
+        machine.fail("test -f /run/nixos/activation-reload-list")
+  '';
+})
diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix
index 3fd55444fd8a5..f0ccfe7ea0e6a 100644
--- a/nixos/tests/rspamd.nix
+++ b/nixos/tests/rspamd.nix
@@ -25,7 +25,6 @@ let
     machine = {
       services.rspamd.enable = true;
       networking.enableIPv6 = enableIPv6;
-      virtualisation.memorySize = 1024;
     };
     testScript = ''
       start_all()
@@ -69,7 +68,6 @@ in
           group = "rspamd";
         }];
       };
-      virtualisation.memorySize = 1024;
     };
 
     testScript = ''
@@ -118,7 +116,6 @@ in
           '';
         };
       };
-      virtualisation.memorySize = 1024;
     };
 
     testScript = ''
@@ -224,7 +221,6 @@ in
           rspamd_logger.infox(rspamd_config, 'Work dammit!!!')
         '';
       };
-      virtualisation.memorySize = 1024;
     };
     testScript = ''
       ${initMachine}
@@ -291,7 +287,6 @@ in
         postfix.enable = true;
         workers.rspamd_proxy.type = "rspamd_proxy";
       };
-      virtualisation.memorySize = 1024;
     };
     testScript = ''
       ${initMachine}
diff --git a/nixos/tests/run-in-machine.nix b/nixos/tests/run-in-machine.nix
deleted file mode 100644
index 67840f3e9fe7f..0000000000000
--- a/nixos/tests/run-in-machine.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../.. { inherit system config; }
-}:
-
-with import ../lib/testing-python.nix { inherit system pkgs; };
-
-let
-  output = runInMachine {
-    drv = pkgs.hello;
-    machine = { ... }: { /* services.sshd.enable = true; */ };
-  };
-
-  test = pkgs.runCommand "verify-output" { inherit output; } ''
-    if [ ! -e "$output/bin/hello" ]; then
-      echo "Derivation built using runInMachine produced incorrect output:" >&2
-      ls -laR "$output" >&2
-      exit 1
-    fi
-    "$output/bin/hello" > "$out"
-  '';
-
-in test // { inherit test; } # To emulate behaviour of makeTest
diff --git a/nixos/tests/samba.nix b/nixos/tests/samba.nix
index d1d50caabfa53..252c3dd9c76e9 100644
--- a/nixos/tests/samba.nix
+++ b/nixos/tests/samba.nix
@@ -20,6 +20,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       server =
         { ... }:
         { services.samba.enable = true;
+          services.samba.openFirewall = true;
           services.samba.shares.public =
             { path = "/public";
               "read only" = true;
@@ -27,8 +28,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
               "guest ok" = "yes";
               comment = "Public samba share.";
             };
-          networking.firewall.allowedTCPPorts = [ 139 445 ];
-          networking.firewall.allowedUDPPorts = [ 137 138 ];
         };
     };
 
diff --git a/nixos/tests/seafile.nix b/nixos/tests/seafile.nix
new file mode 100644
index 0000000000000..6eec8b1fbe55c
--- /dev/null
+++ b/nixos/tests/seafile.nix
@@ -0,0 +1,121 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    client = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.seafile-shared pkgs.curl ];
+    };
+  in {
+    name = "seafile";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ kampfschlaefer schmittlauch ];
+    };
+
+    nodes = {
+      server = { config, pkgs, ... }: {
+        services.seafile = {
+          enable = true;
+          ccnetSettings.General.SERVICE_URL = "http://server";
+          adminEmail = "admin@example.com";
+          initialAdminPassword = "seafile_password";
+        };
+        services.nginx = {
+          enable = true;
+          virtualHosts."server" = {
+            locations."/".proxyPass = "http://unix:/run/seahub/gunicorn.sock";
+            locations."/seafhttp" = {
+              proxyPass = "http://127.0.0.1:8082";
+              extraConfig = ''
+                rewrite ^/seafhttp(.*)$ $1 break;
+                client_max_body_size 0;
+                proxy_connect_timeout  36000s;
+                proxy_read_timeout  36000s;
+                proxy_send_timeout  36000s;
+                send_timeout  36000s;
+                proxy_http_version 1.1;
+              '';
+            };
+          };
+        };
+        networking.firewall = { allowedTCPPorts = [ 80 ]; };
+      };
+      client1 = client pkgs;
+      client2 = client pkgs;
+    };
+
+    testScript = ''
+      start_all()
+
+      with subtest("start seaf-server"):
+          server.wait_for_unit("seaf-server.service")
+          server.wait_for_file("/run/seafile/seafile.sock")
+
+      with subtest("start seahub"):
+          server.wait_for_unit("seahub.service")
+          server.wait_for_unit("nginx.service")
+          server.wait_for_file("/run/seahub/gunicorn.sock")
+
+      with subtest("client1 fetch seahub page"):
+          client1.succeed("curl -L http://server | grep 'Log In' >&2")
+
+      with subtest("client1 connect"):
+          client1.wait_for_unit("default.target")
+          client1.succeed("seaf-cli init -d . >&2")
+          client1.succeed("seaf-cli start >&2")
+          client1.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password >&2"
+          )
+
+          libid = client1.succeed(
+              'seaf-cli create -s http://server -n test01 -u admin\@example.com -p seafile_password -t "first test library"'
+          ).strip()
+
+          client1.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password |grep test01"
+          )
+          client1.fail(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password |grep test02"
+          )
+
+          client1.succeed(
+              f"seaf-cli download -l {libid} -s http://server -u admin\@example.com -p seafile_password -d . >&2"
+          )
+
+          client1.sleep(3)
+
+          client1.succeed("seaf-cli status |grep synchronized >&2")
+
+          client1.succeed("ls -la >&2")
+          client1.succeed("ls -la test01 >&2")
+
+          client1.execute("echo bla > test01/first_file")
+
+          client1.sleep(2)
+
+          client1.succeed("seaf-cli status |grep synchronized >&2")
+
+      with subtest("client2 sync"):
+          client2.wait_for_unit("default.target")
+
+          client2.succeed("seaf-cli init -d . >&2")
+          client2.succeed("seaf-cli start >&2")
+
+          client2.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password >&2"
+          )
+
+          libid = client2.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password |grep test01 |cut -d' ' -f 2"
+          ).strip()
+
+          client2.succeed(
+              f"seaf-cli download -l {libid} -s http://server -u admin\@example.com -p seafile_password -d . >&2"
+          )
+
+          client2.sleep(3)
+
+          client2.succeed("seaf-cli status |grep synchronized >&2")
+
+          client2.succeed("ls -la test01 >&2")
+
+          client2.succeed('[ `cat test01/first_file` = "bla" ]')
+    '';
+  })
diff --git a/nixos/tests/service-runner.nix b/nixos/tests/service-runner.nix
index 58f46735f56dc..79d96f739a6c8 100644
--- a/nixos/tests/service-runner.nix
+++ b/nixos/tests/service-runner.nix
@@ -24,7 +24,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         machine.succeed(
             """
             mkdir -p /run/nginx /var/log/nginx /var/cache/nginx
-            ${nodes.machine.config.systemd.services.nginx.runner} &
+            ${nodes.machine.config.systemd.services.nginx.runner} >&2 &
             echo $!>my-nginx.pid
             """
         )
diff --git a/nixos/tests/shattered-pixel-dungeon.nix b/nixos/tests/shattered-pixel-dungeon.nix
index d8c4b44819e40..d4e5de22ab9d0 100644
--- a/nixos/tests/shattered-pixel-dungeon.nix
+++ b/nixos/tests/shattered-pixel-dungeon.nix
@@ -19,7 +19,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   testScript =
     ''
       machine.wait_for_x()
-      machine.execute("shattered-pixel-dungeon &")
+      machine.execute("shattered-pixel-dungeon >&2 &")
       machine.wait_for_window(r"Shattered Pixel Dungeon")
       machine.sleep(5)
       if "Enter" not in machine.get_screen_text():
diff --git a/nixos/tests/signal-desktop.nix b/nixos/tests/signal-desktop.nix
index 379af4d3912b9..8c72306299230 100644
--- a/nixos/tests/signal-desktop.nix
+++ b/nixos/tests/signal-desktop.nix
@@ -29,7 +29,6 @@ in {
     environment.systemPackages = with pkgs; [
       signal-desktop file sqlite sqlcipher-signal
     ];
-    virtualisation.memorySize = 1024;
   };
 
   enableOCR = true;
@@ -41,7 +40,7 @@ in {
     machine.wait_for_x()
 
     # start signal desktop
-    machine.execute("su - alice -c signal-desktop &")
+    machine.execute("su - alice -c signal-desktop >&2 &")
 
     # Wait for the Signal window to appear. Since usually the tests
     # are run sandboxed and therfore with no internet, we can not wait
diff --git a/nixos/tests/soapui.nix b/nixos/tests/soapui.nix
index 205128df91f45..76a87ed5efa1c 100644
--- a/nixos/tests/soapui.nix
+++ b/nixos/tests/soapui.nix
@@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     machine.wait_for_x()
-    machine.succeed("soapui &")
+    machine.succeed("soapui >&2 &")
     machine.wait_for_window(r"SoapUI \d+\.\d+\.\d+")
     machine.sleep(1)
     machine.screenshot("soapui")
diff --git a/nixos/tests/spark/default.nix b/nixos/tests/spark/default.nix
new file mode 100644
index 0000000000000..025c5a5222e71
--- /dev/null
+++ b/nixos/tests/spark/default.nix
@@ -0,0 +1,27 @@
+import ../make-test-python.nix ({...}: {
+  name = "spark";
+
+  nodes = {
+    worker = { nodes, pkgs, ... }: {
+      services.spark.worker = {
+        enable = true;
+        master = "master:7077";
+      };
+    };
+    master = { config, pkgs, ... }: {
+      services.spark.master = {
+        enable = true;
+        bind = "0.0.0.0";
+      };
+      networking.firewall.allowedTCPPorts = [ 22 7077 8080 ];
+    };
+  };
+
+  testScript = ''
+    master.wait_for_unit("spark-master.service")
+    worker.wait_for_unit("spark-worker.service")
+    worker.copy_from_host( "${./spark_sample.py}", "/spark_sample.py" )
+    assert "<title>Spark Master at spark://" in worker.succeed("curl -sSfkL http://master:8080/")
+    worker.succeed("spark-submit --master spark://master:7077 --executor-memory 512m --executor-cores 1 /spark_sample.py")
+  '';
+})
diff --git a/nixos/tests/spark/spark_sample.py b/nixos/tests/spark/spark_sample.py
new file mode 100644
index 0000000000000..c4939451eae04
--- /dev/null
+++ b/nixos/tests/spark/spark_sample.py
@@ -0,0 +1,40 @@
+from pyspark.sql import Row, SparkSession
+from pyspark.sql import functions as F
+from pyspark.sql.functions import udf
+from pyspark.sql.types import *
+from pyspark.sql.functions import explode
+
+def explode_col(weight):
+    return int(weight//10) * [10.0] + ([] if weight%10==0 else [weight%10])
+
+spark = SparkSession.builder.getOrCreate()
+
+dataSchema = [
+    StructField("feature_1", FloatType()),
+    StructField("feature_2", FloatType()),
+    StructField("bias_weight", FloatType())
+]
+
+data = [
+    Row(0.1, 0.2, 10.32),
+    Row(0.32, 1.43, 12.8),
+    Row(1.28, 1.12, 0.23)
+]
+
+df = spark.createDataFrame(spark.sparkContext.parallelize(data), StructType(dataSchema))
+
+normalizing_constant = 100
+sum_bias_weight = df.select(F.sum('bias_weight')).collect()[0][0]
+normalizing_factor = normalizing_constant / sum_bias_weight
+df = df.withColumn('normalized_bias_weight', df.bias_weight * normalizing_factor)
+df = df.drop('bias_weight')
+df = df.withColumnRenamed('normalized_bias_weight', 'bias_weight')
+
+my_udf = udf(lambda x: explode_col(x), ArrayType(FloatType()))
+df1 = df.withColumn('explode_val', my_udf(df.bias_weight))
+df1 = df1.withColumn("explode_val_1", explode(df1.explode_val)).drop("explode_val")
+df1 = df1.drop('bias_weight').withColumnRenamed('explode_val_1', 'bias_weight')
+
+df1.show()
+
+assert(df1.count() == 12)
diff --git a/nixos/tests/spike.nix b/nixos/tests/spike.nix
deleted file mode 100644
index 09035a1564189..0000000000000
--- a/nixos/tests/spike.nix
+++ /dev/null
@@ -1,22 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-let
-  riscvPkgs = import ../.. { crossSystem = pkgs.lib.systems.examples.riscv64-embedded; };
-in
-{
-  name = "spike";
-  meta = with pkgs.lib.maintainers; { maintainers = [ blitz ]; };
-
-  machine = { pkgs, lib, ... }: {
-    environment.systemPackages = [ pkgs.spike riscvPkgs.riscv-pk riscvPkgs.hello ];
-  };
-
-  # Run the RISC-V hello applications using the proxy kernel on the
-  # Spike emulator and see whether we get the expected output.
-  testScript =
-    ''
-      machine.wait_for_unit("multi-user.target")
-      output = machine.succeed("spike -m64 $(which pk) $(which hello)")
-      assert "Hello, world!" in output
-    '';
-})
diff --git a/nixos/tests/sssd-ldap.nix b/nixos/tests/sssd-ldap.nix
index e3119348eac7e..5c58eaef7146f 100644
--- a/nixos/tests/sssd-ldap.nix
+++ b/nixos/tests/sssd-ldap.nix
@@ -1,96 +1,94 @@
-({ pkgs, ... }:
-  let
-    dbDomain = "example.org";
-    dbSuffix = "dc=example,dc=org";
+let
+  dbDomain = "example.org";
+  dbSuffix = "dc=example,dc=org";
 
-    ldapRootUser = "admin";
-    ldapRootPassword = "foobar";
+  ldapRootUser = "admin";
+  ldapRootPassword = "foobar";
 
-    testUser = "alice";
-  in import ./make-test-python.nix {
-    name = "sssd-ldap";
+  testUser = "alice";
+in import ./make-test-python.nix ({pkgs, ...}: {
+  name = "sssd-ldap";
 
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ bbigras ];
-    };
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bbigras ];
+  };
 
-    machine = { pkgs, ... }: {
-      services.openldap = {
-        enable = true;
-        settings = {
-          children = {
-            "cn=schema".includes = [
-              "${pkgs.openldap}/etc/schema/core.ldif"
-              "${pkgs.openldap}/etc/schema/cosine.ldif"
-              "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
-              "${pkgs.openldap}/etc/schema/nis.ldif"
-            ];
-            "olcDatabase={1}mdb" = {
-              attrs = {
-                objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
-                olcDatabase = "{1}mdb";
-                olcDbDirectory = "/var/db/openldap";
-                olcSuffix = dbSuffix;
-                olcRootDN = "cn=${ldapRootUser},${dbSuffix}";
-                olcRootPW = ldapRootPassword;
-              };
+  machine = { pkgs, ... }: {
+    services.openldap = {
+      enable = true;
+      settings = {
+        children = {
+          "cn=schema".includes = [
+            "${pkgs.openldap}/etc/schema/core.ldif"
+            "${pkgs.openldap}/etc/schema/cosine.ldif"
+            "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+            "${pkgs.openldap}/etc/schema/nis.ldif"
+          ];
+          "olcDatabase={1}mdb" = {
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+              olcDatabase = "{1}mdb";
+              olcDbDirectory = "/var/db/openldap";
+              olcSuffix = dbSuffix;
+              olcRootDN = "cn=${ldapRootUser},${dbSuffix}";
+              olcRootPW = ldapRootPassword;
             };
           };
         };
-        declarativeContents = {
-          ${dbSuffix} = ''
-            dn: ${dbSuffix}
-            objectClass: top
-            objectClass: dcObject
-            objectClass: organization
-            o: ${dbDomain}
+      };
+      declarativeContents = {
+        ${dbSuffix} = ''
+          dn: ${dbSuffix}
+          objectClass: top
+          objectClass: dcObject
+          objectClass: organization
+          o: ${dbDomain}
 
-            dn: ou=posix,${dbSuffix}
-            objectClass: top
-            objectClass: organizationalUnit
+          dn: ou=posix,${dbSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
 
-            dn: ou=accounts,ou=posix,${dbSuffix}
-            objectClass: top
-            objectClass: organizationalUnit
+          dn: ou=accounts,ou=posix,${dbSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
 
-            dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix}
-            objectClass: person
-            objectClass: posixAccount
-            # userPassword: somePasswordHash
-            homeDirectory: /home/${testUser}
-            uidNumber: 1234
-            gidNumber: 1234
-            cn: ""
-            sn: ""
-          '';
-        };
+          dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix}
+          objectClass: person
+          objectClass: posixAccount
+          # userPassword: somePasswordHash
+          homeDirectory: /home/${testUser}
+          uidNumber: 1234
+          gidNumber: 1234
+          cn: ""
+          sn: ""
+        '';
       };
+    };
 
-      services.sssd = {
-        enable = true;
-        config = ''
-          [sssd]
-          config_file_version = 2
-          services = nss, pam, sudo
-          domains = ${dbDomain}
+    services.sssd = {
+      enable = true;
+      config = ''
+        [sssd]
+        config_file_version = 2
+        services = nss, pam, sudo
+        domains = ${dbDomain}
 
-          [domain/${dbDomain}]
-          auth_provider = ldap
-          id_provider = ldap
-          ldap_uri = ldap://127.0.0.1:389
-          ldap_search_base = ${dbSuffix}
-          ldap_default_bind_dn = cn=${ldapRootUser},${dbSuffix}
-          ldap_default_authtok_type = password
-          ldap_default_authtok = ${ldapRootPassword}
-        '';
-      };
+        [domain/${dbDomain}]
+        auth_provider = ldap
+        id_provider = ldap
+        ldap_uri = ldap://127.0.0.1:389
+        ldap_search_base = ${dbSuffix}
+        ldap_default_bind_dn = cn=${ldapRootUser},${dbSuffix}
+        ldap_default_authtok_type = password
+        ldap_default_authtok = ${ldapRootPassword}
+      '';
     };
+  };
 
-    testScript = ''
-      machine.start()
-      machine.wait_for_unit("openldap.service")
-      machine.wait_for_unit("sssd.service")
-      machine.succeed("getent passwd ${testUser}")
-    '';
-  }
-)
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("openldap.service")
+    machine.wait_for_unit("sssd.service")
+    machine.succeed("getent passwd ${testUser}")
+  '';
+})
diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix
index 01240ef572a68..3476ebab3e26c 100644
--- a/nixos/tests/sway.nix
+++ b/nixos/tests/sway.nix
@@ -44,7 +44,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     # To test pinentry via gpg-agent:
     programs.gnupg.agent.enable = true;
 
-    virtualisation.memorySize = 1024;
     # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
     virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
   };
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 78adf7ffa7da5..7ea07a390b808 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -7,15 +7,224 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   };
 
   nodes = {
-    machine = { ... }: {
+    machine = { config, pkgs, lib, ... }: {
+      environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff
       users.mutableUsers = false;
+
+      specialisation = {
+        # A system with a simple socket-activated unit
+        simple-socket.configuration = {
+          systemd.services.socket-activated.serviceConfig = {
+            ExecStart = pkgs.writeScript "socket-test.py" /* python */ ''
+              #!${pkgs.python3}/bin/python3
+
+              from socketserver import TCPServer, StreamRequestHandler
+              import socket
+
+              class Handler(StreamRequestHandler):
+                  def handle(self):
+                      self.wfile.write("hello".encode("utf-8"))
+
+              class Server(TCPServer):
+                  def __init__(self, server_address, handler_cls):
+                      # Invoke base but omit bind/listen steps (performed by systemd activation!)
+                      TCPServer.__init__(
+                          self, server_address, handler_cls, bind_and_activate=False)
+                      # Override socket
+                      self.socket = socket.fromfd(3, self.address_family, self.socket_type)
+
+              if __name__ == "__main__":
+                  server = Server(("localhost", 1234), Handler)
+                  server.serve_forever()
+            '';
+          };
+          systemd.sockets.socket-activated = {
+            wantedBy = [ "sockets.target" ];
+            listenStreams = [ "/run/test.sock" ];
+            socketConfig.SocketMode = lib.mkDefault "0777";
+          };
+        };
+
+        # The same system but the socket is modified
+        modified-socket.configuration = {
+          imports = [ config.specialisation.simple-socket.configuration ];
+          systemd.sockets.socket-activated.socketConfig.SocketMode = "0666";
+        };
+
+        # The same system but the service is modified
+        modified-service.configuration = {
+          imports = [ config.specialisation.simple-socket.configuration ];
+          systemd.services.socket-activated.serviceConfig.X-Test = "test";
+        };
+
+        # The same system but both service and socket are modified
+        modified-service-and-socket.configuration = {
+          imports = [ config.specialisation.simple-socket.configuration ];
+          systemd.services.socket-activated.serviceConfig.X-Test = "some_value";
+          systemd.sockets.socket-activated.socketConfig.SocketMode = "0444";
+        };
+
+        # A system with a socket-activated service and some simple services
+        service-and-socket.configuration = {
+          imports = [ config.specialisation.simple-socket.configuration ];
+          systemd.services.simple-service = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+
+          systemd.services.simple-restart-service = {
+            stopIfChanged = false;
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+
+          systemd.services.simple-reload-service = {
+            reloadIfChanged = true;
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+
+          systemd.services.no-restart-service = {
+            restartIfChanged = false;
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        # The same system but with an activation script that restarts all services
+        restart-and-reload-by-activation-script.configuration = {
+          imports = [ config.specialisation.service-and-socket.configuration ];
+          system.activationScripts.restart-and-reload-test = {
+            supportsDryActivation = true;
+            deps = [];
+            text = ''
+              if [ "$NIXOS_ACTION" = dry-activate ]; then
+                f=/run/nixos/dry-activation-restart-list
+              else
+                f=/run/nixos/activation-restart-list
+              fi
+              cat <<EOF >> "$f"
+              simple-service.service
+              simple-restart-service.service
+              simple-reload-service.service
+              no-restart-service.service
+              socket-activated.service
+              EOF
+            '';
+          };
+        };
+
+        # A system with a timer
+        with-timer.configuration = {
+          systemd.timers.test-timer = {
+            wantedBy = [ "timers.target" ];
+            timerConfig.OnCalendar = "@1395716396"; # chosen by fair dice roll
+          };
+          systemd.services.test-timer = {
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        # The same system but with another time
+        with-timer-modified.configuration = {
+          imports = [ config.specialisation.with-timer.configuration ];
+          systemd.timers.test-timer.timerConfig.OnCalendar = lib.mkForce "Fri 2012-11-23 16:00:00";
+        };
+
+        # A system with a systemd mount
+        with-mount.configuration = {
+          systemd.mounts = [
+            {
+              description = "Testmount";
+              what = "tmpfs";
+              type = "tmpfs";
+              where = "/testmount";
+              options = "size=1M";
+              wantedBy = [ "local-fs.target" ];
+            }
+          ];
+        };
+
+        # The same system but with another time
+        with-mount-modified.configuration = {
+          systemd.mounts = [
+            {
+              description = "Testmount";
+              what = "tmpfs";
+              type = "tmpfs";
+              where = "/testmount";
+              options = "size=10M";
+              wantedBy = [ "local-fs.target" ];
+            }
+          ];
+        };
+
+        # A system with a path unit
+        with-path.configuration = {
+          systemd.paths.test-watch = {
+            wantedBy = [ "paths.target" ];
+            pathConfig.PathExists = "/testpath";
+          };
+          systemd.services.test-watch = {
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${pkgs.coreutils}/bin/touch /testpath-modified";
+            };
+          };
+        };
+
+        # The same system but watching another file
+        with-path-modified.configuration = {
+          imports = [ config.specialisation.with-path.configuration ];
+          systemd.paths.test-watch.pathConfig.PathExists = lib.mkForce "/testpath2";
+        };
+
+        # A system with a slice
+        with-slice.configuration = {
+          systemd.slices.testslice.sliceConfig.MemoryMax = "1"; # don't allow memory allocation
+          systemd.services.testservice = {
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              Slice = "testslice.slice";
+            };
+          };
+        };
+
+        # The same system but the slice allows to allocate memory
+        with-slice-non-crashing.configuration = {
+          imports = [ config.specialisation.with-slice.configuration ];
+          systemd.slices.testslice.sliceConfig.MemoryMax = lib.mkForce null;
+        };
+      };
     };
     other = { ... }: {
       users.mutableUsers = true;
     };
   };
 
-  testScript = {nodes, ...}: let
+  testScript = { nodes, ... }: let
     originalSystem = nodes.machine.config.system.build.toplevel;
     otherSystem = nodes.other.config.system.build.toplevel;
 
@@ -27,12 +236,183 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       set -o pipefail
       exec env -i "$@" | tee /dev/stderr
     '';
-  in ''
+  in /* python */ ''
+    def switch_to_specialisation(name, action="test"):
+        out = machine.succeed(f"${originalSystem}/specialisation/{name}/bin/switch-to-configuration {action} 2>&1")
+        assert_lacks(out, "switch-to-configuration line")  # Perl warnings
+        return out
+
+    def assert_contains(haystack, needle):
+        if needle not in haystack:
+            print("The haystack that will cause the following exception is:")
+            print("---")
+            print(haystack)
+            print("---")
+            raise Exception(f"Expected string '{needle}' was not found")
+
+    def assert_lacks(haystack, needle):
+        if needle in haystack:
+            print("The haystack that will cause the following exception is:")
+            print("---")
+            print(haystack, end="")
+            print("---")
+            raise Exception(f"Unexpected string '{needle}' was found")
+
+
     machine.succeed(
         "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test"
     )
     machine.succeed(
         "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test"
     )
+
+    with subtest("systemd sockets"):
+        machine.succeed("${originalSystem}/bin/switch-to-configuration test")
+
+        # Simple socket is created
+        out = switch_to_specialisation("simple-socket")
+        assert_lacks(out, "stopping the following units:")
+        # not checking for reload because dbus gets reloaded
+        assert_lacks(out, "restarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: socket-activated.socket\n")
+        assert_lacks(out, "as well:")
+        machine.succeed("[ $(stat -c%a /run/test.sock) = 777 ]")
+
+        # Changing the socket restarts it
+        out = switch_to_specialisation("modified-socket")
+        assert_lacks(out, "stopping the following units:")
+        #assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "restarting the following units: socket-activated.socket\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+        machine.succeed("[ $(stat -c%a /run/test.sock) = 666 ]")  # change was applied
+
+        # The unit is properly activated when the socket is accessed
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated")
+
+        # Changing the socket restarts it and ignores the active service
+        out = switch_to_specialisation("simple-socket")
+        assert_contains(out, "stopping the following units: socket-activated.service\n")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "restarting the following units: socket-activated.socket\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+        machine.succeed("[ $(stat -c%a /run/test.sock) = 777 ]")  # change was applied
+
+        # Changing the service does nothing when the service is not active
+        out = switch_to_specialisation("modified-service")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "restarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+
+        # Activating the service and modifying it stops it but leaves the socket untouched
+        machine.succeed("socat - UNIX-CONNECT:/run/test.sock")
+        out = switch_to_specialisation("simple-socket")
+        assert_contains(out, "stopping the following units: socket-activated.service\n")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "restarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+
+        # Activating the service and both the service and the socket stops the service and restarts the socket
+        machine.succeed("socat - UNIX-CONNECT:/run/test.sock")
+        out = switch_to_specialisation("modified-service-and-socket")
+        assert_contains(out, "stopping the following units: socket-activated.service\n")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "restarting the following units: socket-activated.socket\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+
+    with subtest("restart and reload by activation file"):
+        out = switch_to_specialisation("service-and-socket")
+        # Switch to a system where the example services get restarted
+        # by the activation script
+        out = switch_to_specialisation("restart-and-reload-by-activation-script")
+        assert_lacks(out, "stopping the following units:")
+        assert_contains(out, "stopping the following units as well: simple-service.service, socket-activated.service\n")
+        assert_contains(out, "reloading the following units: simple-reload-service.service\n")
+        assert_contains(out, "restarting the following units: simple-restart-service.service\n")
+        assert_contains(out, "\nstarting the following units: simple-service.service")
+
+        # The same, but in dry mode
+        switch_to_specialisation("service-and-socket")
+        out = switch_to_specialisation("restart-and-reload-by-activation-script", action="dry-activate")
+        assert_lacks(out, "would stop the following units:")
+        assert_contains(out, "would stop the following units as well: simple-service.service, socket-activated.service\n")
+        assert_contains(out, "would reload the following units: simple-reload-service.service\n")
+        assert_contains(out, "would restart the following units: simple-restart-service.service\n")
+        assert_contains(out, "\nwould start the following units: simple-service.service")
+
+    with subtest("mounts"):
+        switch_to_specialisation("with-mount")
+        out = machine.succeed("mount | grep 'on /testmount'")
+        assert_contains(out, "size=1024k")
+
+        out = switch_to_specialisation("with-mount-modified")
+        assert_lacks(out, "stopping the following units:")
+        assert_contains(out, "reloading the following units: testmount.mount\n")
+        assert_lacks(out, "restarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+        # It changed
+        out = machine.succeed("mount | grep 'on /testmount'")
+        assert_contains(out, "size=10240k")
+
+    with subtest("timers"):
+        switch_to_specialisation("with-timer")
+        out = machine.succeed("systemctl show test-timer.timer")
+        assert_contains(out, "OnCalendar=2014-03-25 02:59:56 UTC")
+
+        out = switch_to_specialisation("with-timer-modified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "restarting the following units: test-timer.timer\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+        # It changed
+        out = machine.succeed("systemctl show test-timer.timer")
+        assert_contains(out, "OnCalendar=Fri 2012-11-23 16:00:00")
+
+    with subtest("paths"):
+        switch_to_specialisation("with-path")
+        machine.fail("test -f /testpath-modified")
+
+        # touch the file, unit should be triggered
+        machine.succeed("touch /testpath")
+        machine.wait_until_succeeds("test -f /testpath-modified")
+
+        machine.succeed("rm /testpath")
+        machine.succeed("rm /testpath-modified")
+        switch_to_specialisation("with-path-modified")
+
+        machine.succeed("touch /testpath")
+        machine.fail("test -f /testpath-modified")
+        machine.succeed("touch /testpath2")
+        machine.wait_until_succeeds("test -f /testpath-modified")
+
+    # This test ensures that changes to slice configuration get applied.
+    # We test this by having a slice that allows no memory allocation at
+    # all and starting a service within it. If the service crashes, the slice
+    # is applied and if we modify the slice to allow memory allocation, the
+    # service should successfully start.
+    with subtest("slices"):
+        machine.succeed("echo 0 > /proc/sys/vm/panic_on_oom")  # allow OOMing
+        out = switch_to_specialisation("with-slice")
+        machine.fail("systemctl start testservice.service")
+        out = switch_to_specialisation("with-slice-non-crashing")
+        machine.succeed("systemctl start testservice.service")
+        machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom")  # disallow OOMing
+
   '';
 })
diff --git a/nixos/tests/sympa.nix b/nixos/tests/sympa.nix
index eb38df180a789..aad7c95b6c99c 100644
--- a/nixos/tests/sympa.nix
+++ b/nixos/tests/sympa.nix
@@ -5,7 +5,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   machine =
     { ... }:
     {
-      virtualisation.memorySize = 1024;
 
       services.sympa = {
         enable = true;
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index 3c93cb82d646d..a6742606dbefc 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -39,6 +39,29 @@ in
     '';
   };
 
+  # Check that specialisations create corresponding boot entries.
+  specialisation = makeTest {
+    name = "systemd-boot-specialisation";
+    meta.maintainers = with pkgs.lib.maintainers; [ lukegb ];
+
+    machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      specialisation.something.configuration = {};
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed(
+          "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
+      )
+      machine.succeed(
+          "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
+      )
+    '';
+  };
+
   # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
   fallback = makeTest {
     name = "systemd-boot-fallback";
diff --git a/nixos/tests/systemd-confinement.nix b/nixos/tests/systemd-confinement.nix
index e6a308f46d27d..8fafb11e1e8cd 100644
--- a/nixos/tests/systemd-confinement.nix
+++ b/nixos/tests/systemd-confinement.nix
@@ -44,30 +44,26 @@ import ./make-test-python.nix {
       { config.confinement.mode = "chroot-only";
         testScript = ''
           with subtest("chroot-only confinement"):
-              machine.succeed(
-                  'test "$(chroot-exec ls -1 / | paste -sd,)" = bin,nix',
-                  'test "$(chroot-exec id -u)" = 0',
-                  "chroot-exec chown 65534 /bin",
-              )
+              paths = machine.succeed('chroot-exec ls -1 / | paste -sd,').strip()
+              assert_eq(paths, "bin,nix,run")
+              uid = machine.succeed('chroot-exec id -u').strip()
+              assert_eq(uid, "0")
+              machine.succeed("chroot-exec chown 65534 /bin")
         '';
       }
       { testScript = ''
           with subtest("full confinement with APIVFS"):
-              machine.fail(
-                  "chroot-exec ls -l /etc",
-                  "chroot-exec ls -l /run",
-                  "chroot-exec chown 65534 /bin",
-              )
-              machine.succeed(
-                  'test "$(chroot-exec id -u)" = 0',
-                  "chroot-exec chown 0 /bin",
-              )
+              machine.fail("chroot-exec ls -l /etc")
+              machine.fail("chroot-exec chown 65534 /bin")
+              assert_eq(machine.succeed('chroot-exec id -u').strip(), "0")
+              machine.succeed("chroot-exec chown 0 /bin")
         '';
       }
       { config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
         testScript = ''
           with subtest("check existence of bind-mounted /etc"):
-              machine.succeed('test -n "$(chroot-exec cat /etc/passwd)"')
+              passwd = machine.succeed('chroot-exec cat /etc/passwd').strip()
+              assert len(passwd) > 0, "/etc/passwd must not be empty"
         '';
       }
       { config.serviceConfig.User = "chroot-testuser";
@@ -75,7 +71,8 @@ import ./make-test-python.nix {
         testScript = ''
           with subtest("check if User/Group really runs as non-root"):
               machine.succeed("chroot-exec ls -l /dev")
-              machine.succeed('test "$(chroot-exec id -u)" != 0')
+              uid = machine.succeed('chroot-exec id -u').strip()
+              assert uid != "0", "UID of chroot-testuser shouldn't be 0"
               machine.fail("chroot-exec touch /bin/test")
         '';
       }
@@ -88,10 +85,8 @@ import ./make-test-python.nix {
         testScript = ''
           with subtest("check if symlinks are properly bind-mounted"):
               machine.fail("chroot-exec test -e /etc")
-              machine.succeed(
-                  "chroot-exec cat ${symlink} >&2",
-                  'test "$(chroot-exec cat ${symlink})" = "got me"',
-              )
+              text = machine.succeed('chroot-exec cat ${symlink}').strip()
+              assert_eq(text, "got me")
         '';
       })
       { config.serviceConfig.User = "chroot-testuser";
@@ -158,6 +153,9 @@ import ./make-test-python.nix {
   };
 
   testScript = { nodes, ... }: ''
+    def assert_eq(a, b):
+        assert a == b, f"{a} != {b}"
+
     machine.wait_for_unit("multi-user.target")
   '' + nodes.machine.config.__testSteps;
 }
diff --git a/nixos/tests/systemd-cryptenroll.nix b/nixos/tests/systemd-cryptenroll.nix
new file mode 100644
index 0000000000000..49634ef65672c
--- /dev/null
+++ b/nixos/tests/systemd-cryptenroll.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "systemd-cryptenroll";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ymatsiuk ];
+  };
+
+  machine = { pkgs, lib, ... }: {
+    environment.systemPackages = [ pkgs.cryptsetup ];
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      qemu.options = [
+        "-chardev socket,id=chrtpm,path=/tmp/swtpm-sock"
+        "-tpmdev emulator,id=tpm0,chardev=chrtpm"
+        "-device tpm-tis,tpmdev=tpm0"
+      ];
+    };
+  };
+
+  testScript = ''
+    import subprocess
+    import tempfile
+
+    def start_swtpm(tpmstate):
+        subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", "socket", "--tpmstate", "dir="+tpmstate, "--ctrl", "type=unixio,path=/tmp/swtpm-sock", "--log", "level=0", "--tpm2"])
+
+    with tempfile.TemporaryDirectory() as tpmstate:
+        start_swtpm(tpmstate)
+        machine.start()
+
+        # Verify the TPM device is available and accessible by systemd-cryptenroll
+        machine.succeed("test -e /dev/tpm0")
+        machine.succeed("test -e /dev/tpmrm0")
+        machine.succeed("systemd-cryptenroll --tpm2-device=list")
+
+        # Create LUKS partition
+        machine.succeed("echo -n lukspass | cryptsetup luksFormat -q /dev/vdb -")
+        # Enroll new LUKS key and bind it to Secure Boot state
+        # For more details on PASSWORD variable, check the following issue:
+        # https://github.com/systemd/systemd/issues/20955
+        machine.succeed("PASSWORD=lukspass systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/vdb")
+        # Add LUKS partition to /etc/crypttab to test auto unlock
+        machine.succeed("echo 'luks /dev/vdb - tpm2-device=auto' >> /etc/crypttab")
+        machine.shutdown()
+
+        start_swtpm(tpmstate)
+        machine.start()
+
+        # Test LUKS partition automatic unlock on boot
+        machine.wait_for_unit("systemd-cryptsetup@luks.service")
+        # Wipe TPM2 slot
+        machine.succeed("systemd-cryptenroll --wipe-slot=tpm2 /dev/vdb")
+  '';
+})
+
diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index 94f17605e0013..68836c7307297 100644
--- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -42,6 +42,8 @@ import ./make-test-python.nix ({pkgs, ...}: {
       # DO NOT COPY THIS TO PRODUCTION AS IS. Think about it at least twice.
       # Everyone on the "isp" machine will be able to add routes to the kernel.
       security.wrappers.add-dhcpd-lease = {
+        owner = "root";
+        group = "root";
         source = pkgs.writeShellScript "add-dhcpd-lease" ''
           exec ${pkgs.iproute2}/bin/ip -6 route replace "$1" via "$2"
         '';
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index e0685f53a9454..6561f7efe1a5f 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -5,7 +5,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     imports = [ common/user-account.nix common/x11.nix ];
 
     virtualisation.emptyDiskImages = [ 512 512 ];
-    virtualisation.memorySize = 1024;
 
     environment.systemPackages = [ pkgs.cryptsetup ];
 
diff --git a/nixos/tests/tigervnc.nix b/nixos/tests/tigervnc.nix
index c0a52808b2791..ed575682d9338 100644
--- a/nixos/tests/tigervnc.nix
+++ b/nixos/tests/tigervnc.nix
@@ -6,7 +6,7 @@
 with import ../lib/testing-python.nix { inherit system pkgs; };
 makeTest {
   name = "tigervnc";
-  meta = with pkgs.stdenv.lib.maintainers; {
+  meta = with pkgs.lib.maintainers; {
     maintainers = [ lheckemann ];
   };
 
@@ -35,13 +35,13 @@ makeTest {
     for host in [server, client]:
         host.succeed("echo foobar | vncpasswd -f > vncpasswd")
 
-    server.succeed("Xvnc -geometry 720x576 :1 -PasswordFile vncpasswd &")
+    server.succeed("Xvnc -geometry 720x576 :1 -PasswordFile vncpasswd >&2 &")
     server.wait_until_succeeds("nc -z localhost 5901", timeout=10)
     server.succeed("DISPLAY=:1 xwininfo -root | grep 720x576")
-    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC WORLD' &")
+    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC WORLD' >&2 &")
 
     client.wait_for_x()
-    client.execute("vncviewer server:1 -PasswordFile vncpasswd &")
+    client.execute("vncviewer server:1 -PasswordFile vncpasswd >&2 &")
     client.wait_for_window(r"VNC")
     client.screenshot("screenshot")
     text = client.get_screen_text()
diff --git a/nixos/tests/tinydns.nix b/nixos/tests/tinydns.nix
index b80e3451700a6..124508bc004ba 100644
--- a/nixos/tests/tinydns.nix
+++ b/nixos/tests/tinydns.nix
@@ -21,6 +21,20 @@ import ./make-test-python.nix ({ lib, ...} : {
   testScript = ''
     nameserver.start()
     nameserver.wait_for_unit("tinydns.service")
-    nameserver.succeed("host bla.foo.bar 192.168.1.1 | grep '1\.2\.3\.4'")
+
+    # We query tinydns a few times to trigger the bug:
+    #
+    #   nameserver # [    6.105872] mmap: tinydns (842): VmData 331776 exceed data ulimit 300000. Update limits or use boot option ignore_rlimit_data.
+    #
+    # which was reported in https://github.com/NixOS/nixpkgs/issues/119066.
+    # Without the patch <nixpkgs/pkgs/tools/networking/djbdns/softlimit.patch>
+    # it fails on the 10th iteration.
+    nameserver.succeed(
+        """
+          for i in {1..100}; do
+            host bla.foo.bar 192.168.1.1 | grep '1\.2\.3\.4'
+          done
+        """
+    )
   '';
 })
diff --git a/nixos/tests/turbovnc-headless-server.nix b/nixos/tests/turbovnc-headless-server.nix
index dfa17d65f85e4..7d705c56ecf31 100644
--- a/nixos/tests/turbovnc-headless-server.nix
+++ b/nixos/tests/turbovnc-headless-server.nix
@@ -97,7 +97,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         )
         machine.execute(
             # Note trailing & for backgrounding.
-            f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr &",
+            f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr >&2 &",
         )
 
 
@@ -119,7 +119,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     def test_glxgears_failing_with_bad_driver_path():
         machine.execute(
             # Note trailing & for backgrounding.
-            "(env DISPLAY=:0 LIBGL_DRIVERS_PATH=/nonexistent glxgears -info | tee /tmp/glxgears-should-fail.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears-should-fail.stderr &"
+            "(env DISPLAY=:0 LIBGL_DRIVERS_PATH=/nonexistent glxgears -info | tee /tmp/glxgears-should-fail.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears-should-fail.stderr >&2 &"
         )
         machine.wait_until_succeeds("test -f /tmp/glxgears-should-fail.stderr")
         wait_until_terminated_or_succeeds(
@@ -136,7 +136,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     def test_glxgears_prints_renderer():
         machine.execute(
             # Note trailing & for backgrounding.
-            "(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr &"
+            "(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr >&2 &"
         )
         machine.wait_until_succeeds("test -f /tmp/glxgears.stderr")
         wait_until_terminated_or_succeeds(
diff --git a/nixos/tests/tuxguitar.nix b/nixos/tests/tuxguitar.nix
index 6586132d3cd4a..63a7b6c7dec9b 100644
--- a/nixos/tests/tuxguitar.nix
+++ b/nixos/tests/tuxguitar.nix
@@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     machine.wait_for_x()
-    machine.succeed("tuxguitar &")
+    machine.succeed("tuxguitar >&2 &")
     machine.wait_for_window("TuxGuitar - Untitled.tg")
     machine.sleep(1)
     machine.screenshot("tuxguitar")
diff --git a/nixos/tests/ucg.nix b/nixos/tests/ucg.nix
deleted file mode 100644
index 7769fd01fce42..0000000000000
--- a/nixos/tests/ucg.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "ucg";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ AndersonTorres ];
-  };
-
-  machine = { pkgs, ... }: {
-    environment.systemPackages = [ pkgs.ucg ];
-  };
-
-  testScript = ''
-    machine.succeed("echo 'Lorem ipsum dolor sit amet\n2.7182818284590' > /tmp/foo")
-    assert "dolor" in machine.succeed("ucg 'dolor' /tmp/foo")
-    assert "Lorem" in machine.succeed("ucg --ignore-case 'lorem' /tmp/foo")
-    machine.fail("ucg --word-regexp '2718' /tmp/foo")
-    machine.fail("ucg 'pisum' /tmp/foo")
-  '';
-})
diff --git a/nixos/tests/udisks2.nix b/nixos/tests/udisks2.nix
index 1f01cc6de4d6f..6c4b71aaa2eda 100644
--- a/nixos/tests/udisks2.nix
+++ b/nixos/tests/udisks2.nix
@@ -34,7 +34,7 @@ in
 
       with lzma.open(
           "${stick}"
-      ) as data, open(machine.state_dir + "/usbstick.img", "wb") as stick:
+      ) as data, open(machine.state_dir / "usbstick.img", "wb") as stick:
           stick.write(data.read())
 
       machine.succeed("udisksctl info -b /dev/vda >&2")
diff --git a/nixos/tests/unbound.nix b/nixos/tests/unbound.nix
index 58a717f98a161..576287a9fe5d7 100644
--- a/nixos/tests/unbound.nix
+++ b/nixos/tests/unbound.nix
@@ -145,13 +145,22 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
           # user that is permitted to access the unix socket
           someuser = {
             isSystemUser = true;
+            group = "someuser";
             extraGroups = [
               config.users.users.unbound.group
             ];
           };
 
           # user that is not permitted to access the unix socket
-          unauthorizeduser = { isSystemUser = true; };
+          unauthorizeduser = {
+            isSystemUser = true;
+            group = "unauthorizeduser";
+          };
+
+        };
+        users.groups = {
+          someuser = {};
+          unauthorizeduser = {};
         };
 
         # Used for testing configuration reloading
diff --git a/nixos/tests/usbguard.nix b/nixos/tests/usbguard.nix
index cba905db44f34..bb707bdbf7024 100644
--- a/nixos/tests/usbguard.nix
+++ b/nixos/tests/usbguard.nix
@@ -22,7 +22,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     # create a blank disk image for our fake USB stick
-    with open(machine.state_dir + "/usbstick.img", "wb") as stick:
+    with open(machine.state_dir / "usbstick.img", "wb") as stick:
         stick.write(b"\x00" * (1024 * 1024))
 
     # wait for machine to have started and the usbguard service to be up
diff --git a/nixos/tests/user-activation-scripts.nix b/nixos/tests/user-activation-scripts.nix
new file mode 100644
index 0000000000000..0de8664c5ef07
--- /dev/null
+++ b/nixos/tests/user-activation-scripts.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "user-activation-scripts";
+  meta = with lib.maintainers; { maintainers = [ chkno ]; };
+
+  machine = {
+    system.userActivationScripts.foo = "mktemp ~/user-activation-ran.XXXXXX";
+    users.users.alice = {
+      initialPassword = "pass1";
+      isNormalUser = true;
+    };
+  };
+
+  testScript = ''
+    def verify_user_activation_run_count(n):
+        machine.succeed(
+            '[[ "$(find /home/alice/ -name user-activation-ran.\\* | wc -l)" == %s ]]' % n
+        )
+
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("getty@tty1.service")
+    machine.wait_until_tty_matches(1, "login: ")
+    machine.send_chars("alice\n")
+    machine.wait_until_tty_matches(1, "Password: ")
+    machine.send_chars("pass1\n")
+    machine.send_chars("touch login-ok\n")
+    machine.wait_for_file("/home/alice/login-ok")
+    verify_user_activation_run_count(1)
+
+    machine.succeed("/run/current-system/bin/switch-to-configuration test")
+    verify_user_activation_run_count(2)
+  '';
+})
diff --git a/nixos/tests/vault-postgresql.nix b/nixos/tests/vault-postgresql.nix
index a563aead22a3b..071cfd106ffbc 100644
--- a/nixos/tests/vault-postgresql.nix
+++ b/nixos/tests/vault-postgresql.nix
@@ -12,7 +12,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ lnl7 roberth ];
   };
   machine = { lib, pkgs, ... }: {
-    virtualisation.memorySize = 512;
     environment.systemPackages = [ pkgs.vault ];
     environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
     services.vault.enable = true;
diff --git a/nixos/tests/vault.nix b/nixos/tests/vault.nix
index c3b28b62695ad..e86acd5b593fb 100644
--- a/nixos/tests/vault.nix
+++ b/nixos/tests/vault.nix
@@ -8,7 +8,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
     environment.systemPackages = [ pkgs.vault ];
     environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
     services.vault.enable = true;
-    virtualisation.memorySize = 512;
   };
 
   testScript =
diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix
index b5343f5cad2d7..56f1d245d5052 100644
--- a/nixos/tests/vaultwarden.nix
+++ b/nixos/tests/vaultwarden.nix
@@ -140,7 +140,6 @@ let
               in
               [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
 
-            virtualisation.memorySize = 768;
           }
         ];
 
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 09314d93b7d04..f15412d365fa9 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -430,7 +430,7 @@ in mapAttrs (mkVBoxTest false vboxVMs) {
 
 
     create_vm_simple()
-    machine.succeed(ru("VirtualBox &"))
+    machine.succeed(ru("VirtualBox >&2 &"))
     machine.wait_until_succeeds(ru("xprop -name 'Oracle VM VirtualBox Manager'"))
     machine.sleep(5)
     machine.screenshot("gui_manager_started")
diff --git a/nixos/tests/vscodium.nix b/nixos/tests/vscodium.nix
index ca75da35b1e19..43a0d61c856f5 100644
--- a/nixos/tests/vscodium.nix
+++ b/nixos/tests/vscodium.nix
@@ -1,47 +1,69 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+let
+  tests = {
+    wayland = { pkgs, ... }: {
+      imports = [ ./common/wayland-cage.nix ];
 
-{
-  name = "vscodium";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ turion ];
+      services.cage.program = ''
+        ${pkgs.vscodium}/bin/codium \
+          --enable-features=UseOzonePlatform \
+          --ozone-platform=wayland
+      '';
+
+      fonts.fonts = with pkgs; [ dejavu_fonts ];
+    };
+    xorg = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ./common/x11.nix ];
+
+      virtualisation.memorySize = 2047;
+      services.xserver.enable = true;
+      services.xserver.displayManager.sessionCommands = ''
+        ${pkgs.vscodium}/bin/codium
+      '';
+      test-support.displayManager.auto.user = "alice";
+    };
   };
 
-  machine = { ... }:
+  mkTest = name: machine:
+    import ./make-test-python.nix ({ pkgs, ... }: {
+      inherit name;
 
-  {
-    imports = [
-      ./common/user-account.nix
-      ./common/x11.nix
-    ];
+      nodes = { "${name}" = machine; };
 
-    virtualisation.memorySize = 2047;
-    services.xserver.enable = true;
-    test-support.displayManager.auto.user = "alice";
-    environment.systemPackages = with pkgs; [
-      vscodium
-    ];
-  };
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ synthetica turion ];
+      };
+      enableOCR = true;
+      testScript = ''
+        start_all()
+
+        machine.wait_for_unit('graphical.target')
+        machine.wait_until_succeeds('pgrep -x codium')
 
-  enableOCR = true;
+        # Wait until vscodium is visible. "File" is in the menu bar.
+        machine.wait_for_text('File')
+        machine.screenshot('start_screen')
 
-  testScript = { nodes, ... }: ''
-    # Start up X
-    start_all()
-    machine.wait_for_x()
+        test_string = 'testfile'
 
-    # Start VSCodium with a file that doesn't exist yet
-    machine.fail("ls /home/alice/foo.txt")
-    machine.succeed("su - alice -c 'codium foo.txt' &")
+        # Create a new file
+        machine.send_key('ctrl-n')
+        machine.wait_for_text('Untitled')
+        machine.screenshot('empty_editor')
 
-    # Wait for the window to appear
-    machine.wait_for_text("VSCodium")
+        # Type a string
+        machine.send_chars(test_string)
+        machine.wait_for_text(test_string)
+        machine.screenshot('editor')
 
-    # Save file
-    machine.send_key("ctrl-s")
+        # Save the file
+        machine.send_key('ctrl-s')
+        machine.wait_for_text('Save')
+        machine.screenshot('save_window')
+        machine.send_key('ret')
 
-    # Wait until the file has been saved
-    machine.wait_for_file("/home/alice/foo.txt")
+        # (the default filename is the first line of the file)
+        machine.wait_for_file(f'/home/alice/{test_string}')
+      '';
+    });
 
-    machine.screenshot("VSCodium")
-  '';
-})
+in builtins.mapAttrs (k: v: mkTest k v { }) tests
diff --git a/nixos/tests/wasabibackend.nix b/nixos/tests/wasabibackend.nix
index 1832698ab698c..75730fe24d096 100644
--- a/nixos/tests/wasabibackend.nix
+++ b/nixos/tests/wasabibackend.nix
@@ -14,7 +14,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           port = 18332;
         };
       };
-      services.bitcoind = {
+      services.bitcoind."testnet" = {
         enable = true;
         testnet = true;
         rpc.users = {
diff --git a/nixos/tests/web-apps/peertube.nix b/nixos/tests/web-apps/peertube.nix
new file mode 100644
index 0000000000000..38b31f6c3325a
--- /dev/null
+++ b/nixos/tests/web-apps/peertube.nix
@@ -0,0 +1,127 @@
+import ../make-test-python.nix ({pkgs, ...}:
+{
+  name = "peertube";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes = {
+    database = {
+      networking = {
+       interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.10"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 5432 6379 ];
+      };
+
+      services.postgresql = {
+        enable = true;
+        enableTCPIP = true;
+        authentication = ''
+          hostnossl peertube_local peertube_test 192.168.2.11/32 md5
+        '';
+        initialScript = pkgs.writeText "postgresql_init.sql" ''
+          CREATE ROLE peertube_test LOGIN PASSWORD '0gUN0C1mgST6czvjZ8T9';
+          CREATE DATABASE peertube_local TEMPLATE template0 ENCODING UTF8;
+          GRANT ALL PRIVILEGES ON DATABASE peertube_local TO peertube_test;
+          \connect peertube_local
+          CREATE EXTENSION IF NOT EXISTS pg_trgm;
+          CREATE EXTENSION IF NOT EXISTS unaccent;
+        '';
+      };
+
+      services.redis = {
+        enable = true;
+        bind = "0.0.0.0";
+        requirePass = "turrQfaQwnanGbcsdhxy";
+      };
+    };
+
+    server = { pkgs, ... }: {
+      environment = {
+        etc = {
+          "peertube/password-posgressql-db".text = ''
+            0gUN0C1mgST6czvjZ8T9
+          '';
+          "peertube/password-redis-db".text = ''
+            turrQfaQwnanGbcsdhxy
+          '';
+        };
+      };
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.11"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = ''
+          192.168.2.11 peertube.local
+        '';
+        firewall.allowedTCPPorts = [ 9000 ];
+      };
+
+      services.peertube = {
+        enable = true;
+        localDomain = "peertube.local";
+        enableWebHttps = false;
+
+        database = {
+          host = "192.168.2.10";
+          name = "peertube_local";
+          user = "peertube_test";
+          passwordFile = "/etc/peertube/password-posgressql-db";
+        };
+
+        redis = {
+          host = "192.168.2.10";
+          passwordFile = "/etc/peertube/password-redis-db";
+        };
+
+        settings = {
+          listen = {
+            hostname = "0.0.0.0";
+          };
+          instance = {
+            name = "PeerTube Test Server";
+          };
+        };
+      };
+    };
+
+    client = {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+       interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.12"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = ''
+          192.168.2.11 peertube.local
+        '';
+      };
+    };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    database.wait_for_unit("postgresql.service")
+    database.wait_for_unit("redis.service")
+
+    database.wait_for_open_port(5432)
+    database.wait_for_open_port(6379)
+
+    server.wait_for_unit("peertube.service")
+    server.wait_for_open_port(9000)
+
+    # Check if PeerTube is running
+    client.succeed("curl --fail http://peertube.local:9000/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube\ Test\ Server'")
+
+    client.shutdown()
+    server.shutdown()
+    database.shutdown()
+  '';
+})
diff --git a/nixos/tests/wine.nix b/nixos/tests/wine.nix
new file mode 100644
index 0000000000000..c46c7d338b2e2
--- /dev/null
+++ b/nixos/tests/wine.nix
@@ -0,0 +1,41 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (pkgs.lib) concatMapStrings listToAttrs;
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+
+  hello32 = "${pkgs.pkgsCross.mingw32.hello}/bin/hello.exe";
+  hello64 = "${pkgs.pkgsCross.mingwW64.hello}/bin/hello.exe";
+
+  makeWineTest = packageSet: exes: variant: rec {
+    name = "${packageSet}-${variant}";
+    value = makeTest {
+      inherit name;
+      meta = with pkgs.lib.maintainers; { maintainers = [ chkno ]; };
+
+      machine = { pkgs, ... }: {
+        environment.systemPackages = [ pkgs."${packageSet}"."${variant}" ];
+        virtualisation.diskSize = "800";
+      };
+
+      testScript = ''
+        machine.wait_for_unit("multi-user.target")
+        ${concatMapStrings (exe: ''
+          greeting = machine.succeed(
+              "bash -c 'wine ${exe} 2> >(tee wine-stderr >&2)'"
+          )
+          assert 'Hello, world!' in greeting
+          machine.fail(
+              "fgrep 'Could not find Wine Gecko. HTML rendering will be disabled.' wine-stderr"
+          )
+        '') exes}
+      '';
+    };
+  };
+
+  variants = [ "base" "full" "minimal" "staging" "unstable" ];
+
+in listToAttrs (map (makeWineTest "winePackages" [ hello32 ]) variants
+  ++ map (makeWineTest "wineWowPackages" [ hello32 hello64 ]) variants)
diff --git a/nixos/tests/without-nix.nix b/nixos/tests/without-nix.nix
new file mode 100644
index 0000000000000..2fc00b04144f9
--- /dev/null
+++ b/nixos/tests/without-nix.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "without-nix";
+  meta = with lib.maintainers; {
+    maintainers = [ ericson2314 ];
+  };
+
+  nixpkgs.overlays = [
+    (self: super: {
+      nix = throw "don't want to use this";
+    })
+  ];
+
+  nodes.machine = { ... }: {
+    nix.enable = false;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.succeed("which which")
+    machine.fail("which nix")
+  '';
+})
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index 45c58b5b65c80..f7f39668c86ec 100644
--- a/nixos/tests/wordpress.nix
+++ b/nixos/tests/wordpress.nix
@@ -45,6 +45,21 @@ import ./make-test-python.nix ({ pkgs, ... }:
       networking.firewall.allowedTCPPorts = [ 80 ];
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
+
+    wp_caddy = { ... }: {
+      services.wordpress.webserver = "caddy";
+      services.wordpress.sites = {
+        "site1.local" = {
+          database.tablePrefix = "site1_";
+        };
+        "site2.local" = {
+          database.tablePrefix = "site2_";
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
   };
 
   testScript = ''
@@ -54,10 +69,11 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
     wp_httpd.wait_for_unit("httpd")
     wp_nginx.wait_for_unit("nginx")
+    wp_caddy.wait_for_unit("caddy")
 
     site_names = ["site1.local", "site2.local"]
 
-    for machine in (wp_httpd, wp_nginx):
+    for machine in (wp_httpd, wp_nginx, wp_caddy):
         for site_name in site_names:
             machine.wait_for_unit(f"phpfpm-wordpress-{site_name}")
 
diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix
new file mode 100644
index 0000000000000..1d669d5016a70
--- /dev/null
+++ b/nixos/tests/wpa_supplicant.nix
@@ -0,0 +1,81 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+{
+  name = "wpa_supplicant";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    # add a virtual wlan interface
+    boot.kernelModules = [ "mac80211_hwsim" ];
+
+    # wireless access point
+    services.hostapd = {
+      enable = true;
+      wpa = true;
+      interface = "wlan0";
+      ssid = "nixos-test";
+      wpaPassphrase = "reproducibility";
+    };
+
+    # wireless client
+    networking.wireless = {
+      # the override is needed because the wifi is
+      # disabled with mkVMOverride in qemu-vm.nix.
+      enable = lib.mkOverride 0 true;
+      userControlled.enable = true;
+      interfaces = [ "wlan1" ];
+
+      networks = {
+        # test network
+        nixos-test.psk = "@PSK_NIXOS_TEST@";
+
+        # secrets substitution test cases
+        test1.psk = "@PSK_VALID@";              # should be replaced
+        test2.psk = "@PSK_SPECIAL@";            # should be replaced
+        test3.psk = "@PSK_MISSING@";            # should not be replaced
+        test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced
+      };
+
+      # secrets
+      environmentFile = pkgs.writeText "wpa-secrets" ''
+        PSK_NIXOS_TEST="reproducibility"
+        PSK_VALID="S0m3BadP4ssw0rd";
+        # taken from https://github.com/minimaxir/big-list-of-naughty-strings
+        PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~";
+      '';
+    };
+
+  };
+
+  testScript =
+    ''
+      config_file = "/run/wpa_supplicant/wpa_supplicant.conf"
+
+      with subtest("Configuration file is inaccessible to other users"):
+          machine.wait_for_file(config_file)
+          machine.fail(f"sudo -u nobody ls {config_file}")
+
+      with subtest("Secrets variables have been substituted"):
+          machine.fail(f"grep -q @PSK_VALID@ {config_file}")
+          machine.fail(f"grep -q @PSK_SPECIAL@ {config_file}")
+          machine.succeed(f"grep -q @PSK_MISSING@ {config_file}")
+          machine.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}")
+
+          # save file for manual inspection
+          machine.copy_from_vm(config_file)
+
+      with subtest("Daemon is running and accepting connections"):
+          machine.wait_for_unit("wpa_supplicant-wlan1.service")
+          status = machine.succeed("wpa_cli -i wlan1 status")
+          assert "Failed to connect" not in status, \
+                 "Failed to connect to the daemon"
+
+      with subtest("Daemon can connect to the access point"):
+          machine.wait_until_succeeds(
+            "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
+          )
+    '';
+})
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index 99e30342e5937..9051deebae76e 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -23,7 +23,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
       hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
 
-      virtualisation.memorySize = 1024;
     };
 
   testScript = { nodes, ... }: let
@@ -38,7 +37,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       # Check that logging in has given the user ownership of devices.
       machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
 
-      machine.succeed("su - ${user.name} -c 'DISPLAY=:0.0 xfce4-terminal &'")
+      machine.succeed("su - ${user.name} -c 'DISPLAY=:0.0 xfce4-terminal >&2 &'")
       machine.wait_for_window("Terminal")
       machine.sleep(10)
       machine.screenshot("screen")
diff --git a/nixos/tests/xrdp.nix b/nixos/tests/xrdp.nix
index 92eb7d4772ef2..0e1d521c5aced 100644
--- a/nixos/tests/xrdp.nix
+++ b/nixos/tests/xrdp.nix
@@ -32,13 +32,13 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     client.sleep(5)
 
-    client.execute("xterm &")
+    client.execute("xterm >&2 &")
     client.sleep(1)
     client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:${user.name} /p:${user.password}\n")
     client.sleep(5)
     client.screenshot("localrdp")
 
-    client.execute("xterm &")
+    client.execute("xterm >&2 &")
     client.sleep(1)
     client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:${user.name} /p:${user.password}\n")
     client.sleep(5)
diff --git a/nixos/tests/xterm.nix b/nixos/tests/xterm.nix
index 078d1dca96423..4ee31139ab52b 100644
--- a/nixos/tests/xterm.nix
+++ b/nixos/tests/xterm.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   testScript =
     ''
       machine.wait_for_x()
-      machine.succeed("DISPLAY=:0 xterm -title testterm -class testterm -fullscreen &")
+      machine.succeed("DISPLAY=:0 xterm -title testterm -class testterm -fullscreen >&2 &")
       machine.sleep(2)
       machine.send_chars("echo $XTERM_VERSION >> /tmp/xterm_version\n")
       machine.wait_for_file("/tmp/xterm_version")
diff --git a/nixos/tests/yq.nix b/nixos/tests/yq.nix
deleted file mode 100644
index cdcb3d6e2462d..0000000000000
--- a/nixos/tests/yq.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "yq";
-  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
-
-  nodes.yq = { pkgs, ... }: { environment.systemPackages = with pkgs; [ jq yq ]; };
-
-  testScript = ''
-    assert "hello:\n  foo: bar\n" in yq.succeed(
-        'echo \'{"hello":{"foo":"bar"}}\' | yq -y .'
-    )
-  '';
-})