about summary refs log tree commit diff
path: root/nixpkgs/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos')
-rw-r--r--nixpkgs/nixos/doc/manual/administration/cleaning-store.chapter.md4
-rw-r--r--nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/configuration/renaming-interfaces.section.md2
-rw-r--r--nixpkgs/nixos/doc/manual/default.nix2
-rw-r--r--nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md14
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml3
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml2
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml2
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml24
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml5
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml16
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml509
-rw-r--r--nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml315
-rw-r--r--nixpkgs/nixos/doc/manual/installation/installing.chapter.md5
-rw-r--r--nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md16
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-install.xml13
-rw-r--r--nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml8
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md125
-rw-r--r--nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md118
-rw-r--r--nixpkgs/nixos/lib/eval-config.nix55
-rw-r--r--nixpkgs/nixos/lib/make-disk-image.nix88
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/default.nix130
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py37
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/generateCommonMark.py27
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl4
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix6
-rw-r--r--nixpkgs/nixos/lib/make-options-doc/sortXML.py1
-rw-r--r--nixpkgs/nixos/lib/make-squashfs.nix9
-rw-r--r--nixpkgs/nixos/lib/make-zfs-image.nix2
-rw-r--r--nixpkgs/nixos/lib/qemu-common.nix4
-rw-r--r--nixpkgs/nixos/lib/systemd-lib.nix (renamed from nixpkgs/nixos/modules/system/boot/systemd-lib.nix)2
-rw-r--r--nixpkgs/nixos/lib/systemd-unit-options.nix (renamed from nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix)4
-rw-r--r--nixpkgs/nixos/lib/test-driver/default.nix32
-rw-r--r--nixpkgs/nixos/lib/test-driver/setup.py13
-rwxr-xr-xnixpkgs/nixos/lib/test-driver/test_driver/__init__.py100
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/driver.py161
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/logger.py101
-rw-r--r--[-rwxr-xr-x]nixpkgs/nixos/lib/test-driver/test_driver/machine.py (renamed from nixpkgs/nixos/lib/test-driver/test-driver.py)635
-rw-r--r--nixpkgs/nixos/lib/test-driver/test_driver/vlan.py58
-rw-r--r--nixpkgs/nixos/lib/testing-python.nix107
-rw-r--r--nixpkgs/nixos/lib/utils.nix9
-rw-r--r--nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix5
-rwxr-xr-xnixpkgs/nixos/maintainers/scripts/ec2/create-amis.sh21
-rw-r--r--nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix102
-rw-r--r--nixpkgs/nixos/maintainers/scripts/lxd/lxd-image.nix34
-rw-r--r--nixpkgs/nixos/maintainers/scripts/lxd/nix.tpl9
-rw-r--r--nixpkgs/nixos/modules/config/console.nix2
-rw-r--r--nixpkgs/nixos/modules/config/fonts/fontdir.nix1
-rw-r--r--nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix1
-rw-r--r--nixpkgs/nixos/modules/config/i18n.nix6
-rw-r--r--nixpkgs/nixos/modules/config/networking.nix8
-rw-r--r--nixpkgs/nixos/modules/config/swap.nix19
-rw-r--r--nixpkgs/nixos/modules/config/system-path.nix22
-rw-r--r--nixpkgs/nixos/modules/config/users-groups.nix1
-rw-r--r--nixpkgs/nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix47
-rw-r--r--nixpkgs/nixos/modules/hardware/flirc.nix12
-rw-r--r--nixpkgs/nixos/modules/hardware/gkraken.nix18
-rw-r--r--nixpkgs/nixos/modules/hardware/gpgsmartcards.nix37
-rw-r--r--nixpkgs/nixos/modules/hardware/keyboard/zsa.nix3
-rw-r--r--nixpkgs/nixos/modules/hardware/pcmcia.nix1
-rw-r--r--nixpkgs/nixos/modules/hardware/printers.nix9
-rw-r--r--nixpkgs/nixos/modules/hardware/system-76.nix8
-rw-r--r--nixpkgs/nixos/modules/hardware/video/hidpi.nix1
-rw-r--r--nixpkgs/nixos/modules/hardware/video/nvidia.nix8
-rw-r--r--nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix2
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh7
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-enter.sh32
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl8
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-install.sh3
-rw-r--r--nixpkgs/nixos/modules/installer/tools/nixos-version.sh1
-rw-r--r--nixpkgs/nixos/modules/misc/documentation.nix91
-rw-r--r--nixpkgs/nixos/modules/misc/extra-arguments.nix4
-rw-r--r--nixpkgs/nixos/modules/misc/ids.nix8
-rw-r--r--nixpkgs/nixos/modules/misc/locate.nix28
-rw-r--r--nixpkgs/nixos/modules/misc/man-db.nix75
-rw-r--r--nixpkgs/nixos/modules/misc/mandoc.nix61
-rw-r--r--nixpkgs/nixos/modules/misc/meta.nix2
-rw-r--r--nixpkgs/nixos/modules/misc/version.nix4
-rw-r--r--nixpkgs/nixos/modules/module-list.nix40
-rw-r--r--nixpkgs/nixos/modules/profiles/base.nix1
-rw-r--r--nixpkgs/nixos/modules/profiles/minimal.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/bcc.nix6
-rw-r--r--nixpkgs/nixos/modules/programs/captive-browser.nix26
-rw-r--r--nixpkgs/nixos/modules/programs/cnping.nix21
-rw-r--r--nixpkgs/nixos/modules/programs/file-roller.nix17
-rw-r--r--nixpkgs/nixos/modules/programs/firejail.nix6
-rw-r--r--nixpkgs/nixos/modules/programs/gnupg.nix1
-rw-r--r--nixpkgs/nixos/modules/programs/neovim.nix18
-rw-r--r--nixpkgs/nixos/modules/programs/qt5ct.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/ssh.nix11
-rw-r--r--nixpkgs/nixos/modules/programs/sway.nix2
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix8
-rw-r--r--nixpkgs/nixos/modules/programs/zsh/zsh.nix21
-rw-r--r--nixpkgs/nixos/modules/rename.nix6
-rw-r--r--nixpkgs/nixos/modules/security/acme.nix329
-rw-r--r--nixpkgs/nixos/modules/security/acme.xml165
-rw-r--r--nixpkgs/nixos/modules/security/ca.nix19
-rw-r--r--nixpkgs/nixos/modules/security/dhparams.nix6
-rw-r--r--nixpkgs/nixos/modules/security/doas.nix2
-rw-r--r--nixpkgs/nixos/modules/security/pam.nix439
-rw-r--r--nixpkgs/nixos/modules/security/systemd-confinement.nix6
-rw-r--r--nixpkgs/nixos/modules/security/wrappers/default.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/icecast.nix1
-rw-r--r--nixpkgs/nixos/modules/services/audio/mpdscribble.nix13
-rw-r--r--nixpkgs/nixos/modules/services/audio/navidrome.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/roon-server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/audio/snapserver.nix4
-rw-r--r--nixpkgs/nixos/modules/services/audio/ympd.nix1
-rw-r--r--nixpkgs/nixos/modules/services/backup/bacula.nix3
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.nix43
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicati.nix35
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic-rest-server.nix4
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic.nix22
-rw-r--r--nixpkgs/nixos/modules/services/backup/tarsnap.nix35
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix35
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/default.nix132
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix186
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix102
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix2
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix332
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix6
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix7
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix6
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix5
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix10
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix1
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix5
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix4
-rw-r--r--nixpkgs/nixos/modules/services/computing/slurm/slurm.nix10
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix4
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix4
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix61
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix12
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix17
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix77
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix6
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix1
-rw-r--r--nixpkgs/nixos/modules/services/databases/clickhouse.nix17
-rw-r--r--nixpkgs/nixos/modules/services/databases/couchdb.nix16
-rw-r--r--nixpkgs/nixos/modules/services/databases/hbase.nix53
-rw-r--r--nixpkgs/nixos/modules/services/databases/influxdb2.nix21
-rw-r--r--nixpkgs/nixos/modules/services/databases/mysql.nix380
-rw-r--r--nixpkgs/nixos/modules/services/databases/neo4j.nix8
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.nix8
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.xml86
-rw-r--r--nixpkgs/nixos/modules/services/databases/redis.nix488
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gvfs.nix2
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/client-rt.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/client.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/jack.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/media-session.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json (renamed from nixpkgs/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json)0
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix20
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix11
-rw-r--r--nixpkgs/nixos/modules/services/development/blackfire.nix13
-rw-r--r--nixpkgs/nixos/modules/services/development/blackfire.xml3
-rw-r--r--nixpkgs/nixos/modules/services/development/hoogle.nix1
-rw-r--r--nixpkgs/nixos/modules/services/finance/odoo.nix122
-rw-r--r--nixpkgs/nixos/modules/services/games/factorio.nix4
-rw-r--r--nixpkgs/nixos/modules/services/games/quake3-server.nix1
-rw-r--r--nixpkgs/nixos/modules/services/games/terraria.nix5
-rw-r--r--nixpkgs/nixos/modules/services/hardware/bluetooth.nix4
-rw-r--r--nixpkgs/nixos/modules/services/hardware/fancontrol.nix1
-rw-r--r--nixpkgs/nixos/modules/services/hardware/power-profiles-daemon.nix2
-rw-r--r--nixpkgs/nixos/modules/services/hardware/rasdaemon.nix170
-rw-r--r--nixpkgs/nixos/modules/services/hardware/spacenavd.nix1
-rw-r--r--nixpkgs/nixos/modules/services/hardware/tcsd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thinkfan.nix4
-rw-r--r--nixpkgs/nixos/modules/services/logging/filebeat.nix253
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalbeat.nix19
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalwatch.nix1
-rw-r--r--nixpkgs/nixos/modules/services/logging/klogd.nix1
-rw-r--r--nixpkgs/nixos/modules/services/logging/logrotate.nix4
-rw-r--r--nixpkgs/nixos/modules/services/logging/logstash.nix13
-rw-r--r--nixpkgs/nixos/modules/services/mail/dovecot.nix3
-rw-r--r--nixpkgs/nixos/modules/services/mail/maddy.nix273
-rw-r--r--nixpkgs/nixos/modules/services/mail/opendkim.nix1
-rw-r--r--nixpkgs/nixos/modules/services/mail/postfix.nix4
-rw-r--r--nixpkgs/nixos/modules/services/mail/roundcube.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/rspamd.nix5
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mjolnir.nix242
-rw-r--r--nixpkgs/nixos/modules/services/matrix/mjolnir.xml134
-rw-r--r--nixpkgs/nixos/modules/services/matrix/pantalaimon-options.nix70
-rw-r--r--nixpkgs/nixos/modules/services/matrix/pantalaimon.nix70
-rw-r--r--nixpkgs/nixos/modules/services/misc/airsonic.nix5
-rw-r--r--nixpkgs/nixos/modules/services/misc/ananicy.nix107
-rw-r--r--nixpkgs/nixos/modules/services/misc/couchpotato.nix42
-rw-r--r--nixpkgs/nixos/modules/services/misc/dysnomia.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/etcd.nix10
-rw-r--r--nixpkgs/nixos/modules/services/misc/exhibitor.nix5
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitea.nix15
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.nix7
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitweb.nix1
-rw-r--r--nixpkgs/nixos/modules/services/misc/gogs.nix5
-rw-r--r--nixpkgs/nixos/modules/services/misc/gollum.nix1
-rw-r--r--nixpkgs/nixos/modules/services/misc/headphones.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/home-assistant.nix9
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix7
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-synapse.nix42
-rw-r--r--nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix5
-rw-r--r--nixpkgs/nixos/modules/services/misc/mediatomb.nix10
-rw-r--r--nixpkgs/nixos/modules/services/misc/moonraker.nix5
-rw-r--r--nixpkgs/nixos/modules/services/misc/mwlib.nix258
-rw-r--r--nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix9
-rw-r--r--nixpkgs/nixos/modules/services/misc/nitter.nix2
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-daemon.nix90
-rw-r--r--nixpkgs/nixos/modules/services/misc/plex.nix24
-rw-r--r--nixpkgs/nixos/modules/services/misc/rippled.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/sickbeard.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/signald.nix105
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/builds.nix6
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/default.nix1452
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/dispatch.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/git.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/hg.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/hub.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/lists.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/man.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/meta.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/paste.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/service.nix431
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml24
-rw-r--r--nixpkgs/nixos/modules/services/misc/sourcehut/todo.nix4
-rw-r--r--nixpkgs/nixos/modules/services/misc/subsonic.nix9
-rw-r--r--nixpkgs/nixos/modules/services/misc/xmrig.nix76
-rw-r--r--nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix11
-rw-r--r--nixpkgs/nixos/modules/services/misc/zoneminder.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/cadvisor.nix2
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/collectd.nix30
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana.nix9
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/graphite.nix13
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/nagios.nix1
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix7
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix1518
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix1
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix6
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix10
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix11
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix3
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix64
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/smartd.nix7
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/thanos.nix3
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix80
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/uptime.nix10
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix15
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/drbd.nix12
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix2
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix5
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix2
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix144
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/webdav.nix107
-rw-r--r--nixpkgs/nixos/modules/services/networking/adguardhome.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/amuled.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/antennas.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/bind.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/charybdis.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/consul.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/coturn.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/croc.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/ddclient.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpcd.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/ergo.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/eternal-terminal.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix183
-rw-r--r--nixpkgs/nixos/modules/services/networking/firewall.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/flannel.nix3
-rw-r--r--nixpkgs/nixos/modules/services/networking/freeradius.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2pd.nix11
-rw-r--r--nixpkgs/nixos/modules/services/networking/jibri/default.nix417
-rw-r--r--nixpkgs/nixos/modules/services/networking/jibri/logging.properties-journal32
-rw-r--r--nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/kea.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/knot.nix49
-rw-r--r--nixpkgs/nixos/modules/services/networking/lxd-image-server.nix137
-rw-r--r--nixpkgs/nixos/modules/services/networking/monero.nix2
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.md102
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.nix703
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.xml147
-rw-r--r--nixpkgs/nixos/modules/services/networking/ncdns.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/networkmanager.nix8
-rw-r--r--nixpkgs/nixos/modules/services/networking/nix-serve.nix14
-rw-r--r--nixpkgs/nixos/modules/services/networking/nomad.nix19
-rw-r--r--nixpkgs/nixos/modules/services/networking/nsd.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntopng.nix7
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/chrony.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/pleroma.nix11
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.xml2
-rw-r--r--nixpkgs/nixos/modules/services/networking/quassel.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/quorum.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/radicale.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/resilio.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/sabnzbd.nix9
-rw-r--r--nixpkgs/nixos/modules/services/networking/seafile.nix291
-rw-r--r--nixpkgs/nixos/modules/services/networking/shairport-sync.nix31
-rw-r--r--nixpkgs/nixos/modules/services/networking/skydns.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/smokeping.nix54
-rw-r--r--nixpkgs/nixos/modules/services/networking/soju.nix1
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix10
-rw-r--r--nixpkgs/nixos/modules/services/networking/stubby.nix220
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing.nix24
-rw-r--r--nixpkgs/nixos/modules/services/networking/teamspeak3.nix22
-rw-r--r--nixpkgs/nixos/modules/services/networking/tetrd.nix96
-rw-r--r--nixpkgs/nixos/modules/services/networking/tinc.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/unifi.nix91
-rw-r--r--nixpkgs/nixos/modules/services/networking/wasabibackend.nix6
-rw-r--r--nixpkgs/nixos/modules/services/networking/wireguard.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix4
-rw-r--r--nixpkgs/nixos/modules/services/networking/xrdp.nix9
-rw-r--r--nixpkgs/nixos/modules/services/search/kibana.nix6
-rw-r--r--nixpkgs/nixos/modules/services/security/aesmd.nix227
-rw-r--r--nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix1
-rw-r--r--nixpkgs/nixos/modules/services/security/privacyidea.nix7
-rw-r--r--nixpkgs/nixos/modules/services/security/step-ca.nix6
-rw-r--r--nixpkgs/nixos/modules/services/security/tor.nix10
-rw-r--r--nixpkgs/nixos/modules/services/security/torsocks.nix1
-rw-r--r--nixpkgs/nixos/modules/services/security/vault.nix8
-rw-r--r--nixpkgs/nixos/modules/services/security/yubikey-agent.nix8
-rw-r--r--nixpkgs/nixos/modules/services/torrent/peerflix.nix4
-rw-r--r--nixpkgs/nixos/modules/services/torrent/rtorrent.nix4
-rw-r--r--nixpkgs/nixos/modules/services/torrent/transmission.nix362
-rw-r--r--nixpkgs/nixos/modules/services/ttys/getty.nix15
-rw-r--r--nixpkgs/nixos/modules/services/video/epgstation/default.nix12
-rw-r--r--nixpkgs/nixos/modules/services/video/mirakurun.nix2
-rw-r--r--nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix80
-rw-r--r--nixpkgs/nixos/modules/services/video/unifi-video.nix4
-rw-r--r--nixpkgs/nixos/modules/services/wayland/cage.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/bookstack.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/code-server.nix139
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/discourse.nix19
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/discourse.xml4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/galene.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix65
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/hledger-web.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix19
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/invidious.nix264
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix73
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/keycloak.nix5
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/lemmy.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mastodon.nix19
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo.nix34
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mattermost.nix182
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/moinmoin.nix304
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/moodle.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.nix30
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.xml2
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/openwebrx.nix34
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/peertube.nix465
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix5
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/plausible.nix92
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix149
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/trac.nix79
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/tt-rss.nix8
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/youtrack.nix1
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/zabbix.nix11
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix13
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix7
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy/default.nix346
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix71
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix6
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/default.nix34
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix20
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix4
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/uwsgi.nix2
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/varnish/default.nix1
-rw-r--r--nixpkgs/nixos/modules/services/x11/clight.nix8
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix7
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml15
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix15
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix473
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix2
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/default.nix20
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix6
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix4
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix3
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/startx.nix3
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/sx.nix5
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/libinput.nix15
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix7
-rw-r--r--nixpkgs/nixos/modules/services/x11/picom.nix14
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix5
-rw-r--r--nixpkgs/nixos/modules/services/x11/xserver.nix24
-rw-r--r--nixpkgs/nixos/modules/system/activation/activation-script.nix3
-rw-r--r--nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl216
-rw-r--r--nixpkgs/nixos/modules/system/activation/top-level.nix33
-rw-r--r--nixpkgs/nixos/modules/system/boot/binfmt.nix1
-rw-r--r--nixpkgs/nixos/modules/system/boot/kernel.nix2
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix1
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl176
-rw-r--r--nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py93
-rw-r--r--nixpkgs/nixos/modules/system/boot/luksroot.nix1
-rw-r--r--nixpkgs/nixos/modules/system/boot/networkd.nix96
-rw-r--r--nixpkgs/nixos/modules/system/boot/plymouth.nix8
-rw-r--r--nixpkgs/nixos/modules/system/boot/resolved.nix1
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-1-init.sh28
-rw-r--r--nixpkgs/nixos/modules/system/boot/stage-1.nix4
-rwxr-xr-x[-rw-r--r--]nixpkgs/nixos/modules/system/boot/stage-2-init.sh9
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix6
-rw-r--r--nixpkgs/nixos/modules/system/boot/systemd.nix9
-rw-r--r--nixpkgs/nixos/modules/system/boot/timesyncd.nix2
-rw-r--r--nixpkgs/nixos/modules/system/etc/etc.nix5
-rw-r--r--nixpkgs/nixos/modules/system/etc/setup-etc.pl6
-rw-r--r--nixpkgs/nixos/modules/tasks/auto-upgrade.nix1
-rw-r--r--nixpkgs/nixos/modules/tasks/filesystems/zfs.nix11
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix28
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix20
-rw-r--r--nixpkgs/nixos/modules/tasks/network-interfaces.nix71
-rw-r--r--nixpkgs/nixos/modules/tasks/snapraid.nix4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix444
-rw-r--r--nixpkgs/nixos/modules/virtualisation/amazon-options.nix3
-rw-r--r--nixpkgs/nixos/modules/virtualisation/azure-agent.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/cri-o.nix4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker-rootless.nix98
-rw-r--r--nixpkgs/nixos/modules/virtualisation/docker.nix37
-rw-r--r--nixpkgs/nixos/modules/virtualisation/ec2-amis.nix380
-rw-r--r--nixpkgs/nixos/modules/virtualisation/kubevirt.nix30
-rw-r--r--nixpkgs/nixos/modules/virtualisation/libvirtd.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxc-container.nix174
-rw-r--r--nixpkgs/nixos/modules/virtualisation/lxd.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/nixos-containers.nix4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/oci-containers.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/default.nix (renamed from nixpkgs/nixos/modules/virtualisation/podman.nix)4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix (renamed from nixpkgs/nixos/modules/virtualisation/podman-dnsname.nix)0
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix (renamed from nixpkgs/nixos/modules/virtualisation/podman-network-socket-ghostunnel.nix)0
-rw-r--r--nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix (renamed from nixpkgs/nixos/modules/virtualisation/podman-network-socket.nix)4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/proxmox-image.nix169
-rw-r--r--nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix4
-rw-r--r--nixpkgs/nixos/modules/virtualisation/qemu-vm.nix95
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix2
-rw-r--r--nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix10
-rw-r--r--nixpkgs/nixos/modules/virtualisation/vmware-guest.nix13
-rw-r--r--nixpkgs/nixos/modules/virtualisation/waydroid.nix73
-rw-r--r--nixpkgs/nixos/release.nix31
-rw-r--r--nixpkgs/nixos/tests/acme.nix576
-rw-r--r--nixpkgs/nixos/tests/aesmd.nix62
-rw-r--r--nixpkgs/nixos/tests/airsonic.nix4
-rw-r--r--nixpkgs/nixos/tests/all-tests.nix70
-rw-r--r--nixpkgs/nixos/tests/ammonite.nix20
-rw-r--r--nixpkgs/nixos/tests/awscli.nix17
-rw-r--r--nixpkgs/nixos/tests/bat.nix12
-rw-r--r--nixpkgs/nixos/tests/bittorrent.nix2
-rw-r--r--nixpkgs/nixos/tests/boot.nix2
-rw-r--r--nixpkgs/nixos/tests/borgbackup.nix33
-rw-r--r--nixpkgs/nixos/tests/bpf.nix25
-rw-r--r--nixpkgs/nixos/tests/brscan5.nix1
-rw-r--r--nixpkgs/nixos/tests/cage.nix1
-rw-r--r--nixpkgs/nixos/tests/cagebreak.nix1
-rw-r--r--nixpkgs/nixos/tests/cassandra.nix1
-rw-r--r--nixpkgs/nixos/tests/ceph-multi-node.nix1
-rw-r--r--nixpkgs/nixos/tests/ceph-single-node-bluestore.nix1
-rw-r--r--nixpkgs/nixos/tests/ceph-single-node.nix1
-rw-r--r--nixpkgs/nixos/tests/chromium.nix8
-rw-r--r--nixpkgs/nixos/tests/cifs-utils.nix12
-rw-r--r--nixpkgs/nixos/tests/collectd.nix33
-rw-r--r--nixpkgs/nixos/tests/common/acme/client/default.nix6
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/default.nix5
-rw-r--r--nixpkgs/nixos/tests/common/wayland-cage.nix13
-rw-r--r--nixpkgs/nixos/tests/containers-bridge.nix3
-rw-r--r--nixpkgs/nixos/tests/containers-ephemeral.nix1
-rw-r--r--nixpkgs/nixos/tests/containers-extra_veth.nix3
-rw-r--r--nixpkgs/nixos/tests/containers-hosts.nix1
-rw-r--r--nixpkgs/nixos/tests/containers-imperative.nix5
-rw-r--r--nixpkgs/nixos/tests/containers-ip.nix3
-rw-r--r--nixpkgs/nixos/tests/containers-macvlans.nix2
-rw-r--r--nixpkgs/nixos/tests/containers-physical_interfaces.nix4
-rw-r--r--nixpkgs/nixos/tests/containers-portforward.nix3
-rw-r--r--nixpkgs/nixos/tests/containers-tmpfs.nix3
-rw-r--r--nixpkgs/nixos/tests/couchdb.nix3
-rw-r--r--nixpkgs/nixos/tests/croc.nix2
-rw-r--r--nixpkgs/nixos/tests/custom-ca.nix17
-rw-r--r--nixpkgs/nixos/tests/deluge.nix33
-rw-r--r--nixpkgs/nixos/tests/discourse.nix2
-rw-r--r--nixpkgs/nixos/tests/doas.nix8
-rw-r--r--nixpkgs/nixos/tests/docker-rootless.nix41
-rw-r--r--nixpkgs/nixos/tests/docker-tools.nix31
-rw-r--r--nixpkgs/nixos/tests/domination.nix26
-rw-r--r--nixpkgs/nixos/tests/drbd.nix87
-rw-r--r--nixpkgs/nixos/tests/elk.nix143
-rw-r--r--nixpkgs/nixos/tests/emacs-daemon.nix2
-rw-r--r--nixpkgs/nixos/tests/enlightenment.nix3
-rw-r--r--nixpkgs/nixos/tests/etesync-dav.nix2
-rw-r--r--nixpkgs/nixos/tests/fcitx/default.nix1
-rw-r--r--nixpkgs/nixos/tests/fenics.nix1
-rw-r--r--nixpkgs/nixos/tests/firefox.nix7
-rw-r--r--nixpkgs/nixos/tests/ft2-clone.nix2
-rw-r--r--nixpkgs/nixos/tests/gerrit.nix1
-rw-r--r--nixpkgs/nixos/tests/ghostunnel.nix5
-rw-r--r--nixpkgs/nixos/tests/gitlab.nix2
-rw-r--r--nixpkgs/nixos/tests/gnome-xorg.nix1
-rw-r--r--nixpkgs/nixos/tests/gnome.nix1
-rw-r--r--nixpkgs/nixos/tests/graphite.nix1
-rw-r--r--nixpkgs/nixos/tests/hadoop/hadoop.nix228
-rw-r--r--nixpkgs/nixos/tests/hadoop/hdfs.nix41
-rw-r--r--nixpkgs/nixos/tests/hadoop/yarn.nix19
-rw-r--r--nixpkgs/nixos/tests/handbrake.nix16
-rw-r--r--nixpkgs/nixos/tests/hibernate.nix8
-rw-r--r--nixpkgs/nixos/tests/home-assistant.nix20
-rw-r--r--nixpkgs/nixos/tests/hydra/default.nix2
-rw-r--r--nixpkgs/nixos/tests/ihatemoney/default.nix (renamed from nixpkgs/nixos/tests/ihatemoney.nix)32
-rw-r--r--nixpkgs/nixos/tests/ihatemoney/rates.json39
-rw-r--r--nixpkgs/nixos/tests/ihatemoney/server.crt28
-rw-r--r--nixpkgs/nixos/tests/ihatemoney/server.key52
-rw-r--r--nixpkgs/nixos/tests/initrd-secrets.nix10
-rw-r--r--nixpkgs/nixos/tests/installed-tests/default.nix1
-rw-r--r--nixpkgs/nixos/tests/installed-tests/fwupd.nix1
-rw-r--r--nixpkgs/nixos/tests/installed-tests/power-profiles-daemon.nix9
-rw-r--r--nixpkgs/nixos/tests/installer.nix37
-rw-r--r--nixpkgs/nixos/tests/invidious.nix81
-rw-r--r--nixpkgs/nixos/tests/jibri.nix69
-rw-r--r--nixpkgs/nixos/tests/jitsi-meet.nix1
-rw-r--r--nixpkgs/nixos/tests/kafka.nix1
-rw-r--r--nixpkgs/nixos/tests/keepassxc.nix2
-rw-r--r--nixpkgs/nixos/tests/kernel-generic.nix25
-rw-r--r--nixpkgs/nixos/tests/kexec.nix11
-rw-r--r--nixpkgs/nixos/tests/keycloak.nix1
-rw-r--r--nixpkgs/nixos/tests/keymap.nix2
-rw-r--r--nixpkgs/nixos/tests/knot.nix6
-rw-r--r--nixpkgs/nixos/tests/kubernetes/base.nix1
-rw-r--r--nixpkgs/nixos/tests/kubernetes/default.nix2
-rw-r--r--nixpkgs/nixos/tests/kubernetes/dns.nix6
-rw-r--r--nixpkgs/nixos/tests/kubernetes/e2e.nix2
-rw-r--r--nixpkgs/nixos/tests/kubernetes/rbac.nix14
-rw-r--r--nixpkgs/nixos/tests/libinput.nix38
-rw-r--r--nixpkgs/nixos/tests/libresprite.nix30
-rw-r--r--nixpkgs/nixos/tests/libreswan.nix6
-rw-r--r--nixpkgs/nixos/tests/lorri/default.nix2
-rw-r--r--nixpkgs/nixos/tests/lsd.nix12
-rw-r--r--nixpkgs/nixos/tests/lxd-image-server.nix127
-rw-r--r--nixpkgs/nixos/tests/lxd-image.nix89
-rw-r--r--nixpkgs/nixos/tests/lxd.nix4
-rw-r--r--nixpkgs/nixos/tests/maddy.nix58
-rw-r--r--nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix2
-rw-r--r--nixpkgs/nixos/tests/man.nix100
-rw-r--r--nixpkgs/nixos/tests/matrix-appservice-irc.nix141
-rw-r--r--nixpkgs/nixos/tests/matrix/mjolnir.nix165
-rw-r--r--nixpkgs/nixos/tests/matrix/pantalaimon.nix65
-rw-r--r--nixpkgs/nixos/tests/mattermost.nix124
-rw-r--r--nixpkgs/nixos/tests/metabase.nix1
-rw-r--r--nixpkgs/nixos/tests/minecraft.nix2
-rw-r--r--nixpkgs/nixos/tests/misc.nix5
-rw-r--r--nixpkgs/nixos/tests/moinmoin.nix28
-rw-r--r--nixpkgs/nixos/tests/mosquitto.nix217
-rw-r--r--nixpkgs/nixos/tests/mpv.nix2
-rw-r--r--nixpkgs/nixos/tests/mumble.nix4
-rw-r--r--nixpkgs/nixos/tests/musescore.nix2
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql.nix4
-rw-r--r--nixpkgs/nixos/tests/networking-proxy.nix1
-rw-r--r--nixpkgs/nixos/tests/networking.nix71
-rw-r--r--nixpkgs/nixos/tests/nextcloud/default.nix10
-rw-r--r--nixpkgs/nixos/tests/nfs/simple.nix2
-rw-r--r--nixpkgs/nixos/tests/nginx-etag.nix3
-rw-r--r--nixpkgs/nixos/tests/nix-serve-ssh.nix (renamed from nixpkgs/nixos/tests/nix-ssh-serve.nix)2
-rw-r--r--nixpkgs/nixos/tests/nixops/default.nix5
-rw-r--r--nixpkgs/nixos/tests/odoo.nix27
-rw-r--r--nixpkgs/nixos/tests/openarena.nix4
-rw-r--r--nixpkgs/nixos/tests/openresty-lua.nix55
-rw-r--r--nixpkgs/nixos/tests/opensmtpd-rspamd.nix1
-rw-r--r--nixpkgs/nixos/tests/os-prober.nix8
-rw-r--r--nixpkgs/nixos/tests/owncast.nix45
-rw-r--r--nixpkgs/nixos/tests/pam/pam-file-contents.nix25
-rw-r--r--nixpkgs/nixos/tests/pam/pam-oath-login.nix (renamed from nixpkgs/nixos/tests/pam-oath-login.nix)2
-rw-r--r--nixpkgs/nixos/tests/pam/pam-u2f.nix (renamed from nixpkgs/nixos/tests/pam-u2f.nix)2
-rw-r--r--nixpkgs/nixos/tests/pam/test_chfn.py27
-rw-r--r--nixpkgs/nixos/tests/pantheon.nix3
-rw-r--r--nixpkgs/nixos/tests/paperless-ng.nix1
-rw-r--r--nixpkgs/nixos/tests/parsedmarc/default.nix27
-rw-r--r--nixpkgs/nixos/tests/plasma5-systemd-start.nix42
-rw-r--r--nixpkgs/nixos/tests/plasma5.nix7
-rw-r--r--nixpkgs/nixos/tests/pleroma.nix1
-rw-r--r--nixpkgs/nixos/tests/plotinus.nix2
-rw-r--r--nixpkgs/nixos/tests/podman/default.nix (renamed from nixpkgs/nixos/tests/podman.nix)18
-rw-r--r--nixpkgs/nixos/tests/podman/dnsname.nix (renamed from nixpkgs/nixos/tests/podman-dnsname.nix)4
-rw-r--r--nixpkgs/nixos/tests/podman/tls-ghostunnel.nix (renamed from nixpkgs/nixos/tests/podman-tls-ghostunnel.nix)4
-rw-r--r--nixpkgs/nixos/tests/powerdns-admin.nix117
-rw-r--r--nixpkgs/nixos/tests/printing.nix12
-rw-r--r--nixpkgs/nixos/tests/privacyidea.nix1
-rw-r--r--nixpkgs/nixos/tests/prometheus-exporters.nix39
-rw-r--r--nixpkgs/nixos/tests/prometheus.nix92
-rw-r--r--nixpkgs/nixos/tests/pt2-clone.nix2
-rw-r--r--nixpkgs/nixos/tests/pulseaudio.nix71
-rw-r--r--nixpkgs/nixos/tests/rasdaemon.nix34
-rw-r--r--nixpkgs/nixos/tests/redis.nix36
-rw-r--r--nixpkgs/nixos/tests/rspamd.nix5
-rw-r--r--nixpkgs/nixos/tests/sabnzbd.nix22
-rw-r--r--nixpkgs/nixos/tests/samba-wsdd.nix2
-rw-r--r--nixpkgs/nixos/tests/seafile.nix121
-rw-r--r--nixpkgs/nixos/tests/service-runner.nix2
-rw-r--r--nixpkgs/nixos/tests/shattered-pixel-dungeon.nix2
-rw-r--r--nixpkgs/nixos/tests/signal-desktop.nix3
-rw-r--r--nixpkgs/nixos/tests/snapcast.nix8
-rw-r--r--nixpkgs/nixos/tests/soapui.nix2
-rw-r--r--nixpkgs/nixos/tests/sourcehut.nix188
-rw-r--r--nixpkgs/nixos/tests/spark/default.nix1
-rw-r--r--nixpkgs/nixos/tests/spike.nix22
-rw-r--r--nixpkgs/nixos/tests/sssd-ldap.nix160
-rw-r--r--nixpkgs/nixos/tests/step-ca.nix76
-rw-r--r--nixpkgs/nixos/tests/sway.nix1
-rw-r--r--nixpkgs/nixos/tests/switch-test.nix336
-rw-r--r--nixpkgs/nixos/tests/sympa.nix1
-rw-r--r--nixpkgs/nixos/tests/systemd-boot.nix27
-rw-r--r--nixpkgs/nixos/tests/systemd-cryptenroll.nix54
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix81
-rw-r--r--nixpkgs/nixos/tests/systemd.nix13
-rw-r--r--nixpkgs/nixos/tests/tigervnc.nix6
-rw-r--r--nixpkgs/nixos/tests/tinydns.nix16
-rw-r--r--nixpkgs/nixos/tests/trac.nix19
-rw-r--r--nixpkgs/nixos/tests/turbovnc-headless-server.nix6
-rw-r--r--nixpkgs/nixos/tests/tuxguitar.nix2
-rw-r--r--nixpkgs/nixos/tests/txredisapi.nix10
-rw-r--r--nixpkgs/nixos/tests/ucg.nix18
-rw-r--r--nixpkgs/nixos/tests/unifi.nix35
-rw-r--r--nixpkgs/nixos/tests/vault-postgresql.nix3
-rw-r--r--nixpkgs/nixos/tests/vault.nix1
-rw-r--r--nixpkgs/nixos/tests/vaultwarden.nix1
-rw-r--r--nixpkgs/nixos/tests/vengi-tools.nix29
-rw-r--r--nixpkgs/nixos/tests/virtualbox.nix2
-rw-r--r--nixpkgs/nixos/tests/vscodium.nix92
-rw-r--r--nixpkgs/nixos/tests/web-apps/peertube.nix127
-rw-r--r--nixpkgs/nixos/tests/wine.nix41
-rw-r--r--nixpkgs/nixos/tests/xfce.nix3
-rw-r--r--nixpkgs/nixos/tests/xrdp.nix4
-rw-r--r--nixpkgs/nixos/tests/xterm.nix2
-rw-r--r--nixpkgs/nixos/tests/yq.nix12
637 files changed, 21316 insertions, 6949 deletions
diff --git a/nixpkgs/nixos/doc/manual/administration/cleaning-store.chapter.md b/nixpkgs/nixos/doc/manual/administration/cleaning-store.chapter.md
index fb2090b31d84..c9140d0869c7 100644
--- a/nixpkgs/nixos/doc/manual/administration/cleaning-store.chapter.md
+++ b/nixpkgs/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/nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md b/nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md
index 273672fc10ca..0d9d4017ed81 100644
--- a/nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md
+++ b/nixpkgs/nixos/doc/manual/administration/declarative-containers.section.md
@@ -9,7 +9,7 @@ containers.database =
   { config =
       { config, pkgs, ... }:
       { services.postgresql.enable = true;
-      services.postgresql.package = pkgs.postgresql_9_6;
+      services.postgresql.package = pkgs.postgresql_10;
       };
   };
 ```
diff --git a/nixpkgs/nixos/doc/manual/configuration/renaming-interfaces.section.md b/nixpkgs/nixos/doc/manual/configuration/renaming-interfaces.section.md
index b124e8303fee..18390c959b24 100644
--- a/nixpkgs/nixos/doc/manual/configuration/renaming-interfaces.section.md
+++ b/nixpkgs/nixos/doc/manual/configuration/renaming-interfaces.section.md
@@ -26,7 +26,7 @@ we assign the name `wan` to the interface with MAC address
 
 ```nix
 systemd.network.links."10-wan" = {
-  matchConfig.MACAddress = "52:54:00:12:01:01";
+  matchConfig.PermanentMACAddress = "52:54:00:12:01:01";
   linkConfig.Name = "wan";
 };
 ```
diff --git a/nixpkgs/nixos/doc/manual/default.nix b/nixpkgs/nixos/doc/manual/default.nix
index 151743d9fb58..31b6da01c6bd 100644
--- a/nixpkgs/nixos/doc/manual/default.nix
+++ b/nixpkgs/nixos/doc/manual/default.nix
@@ -161,7 +161,7 @@ let
 in rec {
   inherit generatedSources;
 
-  inherit (optionsDoc) optionsJSON optionsXML optionsDocBook;
+  inherit (optionsDoc) optionsJSON optionsDocBook;
 
   # Generate the NixOS manual.
   manualHTML = runCommand "nixos-manual-html"
diff --git a/nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md
index 8471e7608af9..d9749d37da79 100644
--- a/nixpkgs/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixpkgs/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/nixpkgs/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml
index 0ca98dd6e510..4243d2bf53f9 100644
--- a/nixpkgs/nixos/doc/manual/from_md/administration/cleaning-store.chapter.xml
+++ b/nixpkgs/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/nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml b/nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
index a918314a2723..7b35520d567b 100644
--- a/nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
+++ b/nixpkgs/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
@@ -11,7 +11,7 @@ containers.database =
   { config =
       { config, pkgs, ... }:
       { services.postgresql.enable = true;
-      services.postgresql.package = pkgs.postgresql_9_6;
+      services.postgresql.package = pkgs.postgresql_10;
       };
   };
 </programlisting>
diff --git a/nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml b/nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml
index 1c32e30b3f85..88c9e624c82f 100644
--- a/nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml
+++ b/nixpkgs/nixos/doc/manual/from_md/configuration/renaming-interfaces.section.xml
@@ -32,7 +32,7 @@
     </para>
     <programlisting language="bash">
 systemd.network.links.&quot;10-wan&quot; = {
-  matchConfig.MACAddress = &quot;52:54:00:12:01:01&quot;;
+  matchConfig.PermanentMACAddress = &quot;52:54:00:12:01:01&quot;;
   linkConfig.Name = &quot;wan&quot;;
 };
 </programlisting>
diff --git a/nixpkgs/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixpkgs/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
index 83a96d5bb224..0d523681b639 100644
--- a/nixpkgs/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ b/nixpkgs/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/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml
index 91ab71682977..db073fa83965 100644
--- a/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml
+++ b/nixpkgs/nixos/doc/manual/from_md/installation/installing.chapter.xml
@@ -25,8 +25,11 @@
     <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.
+      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
diff --git a/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml b/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
index c0c5a2190fb2..e3b77d4c3650 100644
--- a/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
+++ b/nixpkgs/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
@@ -12,7 +12,7 @@
     <listitem>
       <para>
         <emphasis>Stable channels</emphasis>, such as
-        <link xlink:href="https://nixos.org/channels/nixos-21.05"><literal>nixos-21.05</literal></link>.
+        <link xlink:href="https://nixos.org/channels/nixos-21.11"><literal>nixos-21.11</literal></link>.
         These only get conservative bug fixes and package upgrades. For
         instance, a channel update may cause the Linux kernel on your
         system to be upgraded from 4.19.34 to 4.19.38 (a minor bug fix),
@@ -33,7 +33,7 @@
     <listitem>
       <para>
         <emphasis>Small channels</emphasis>, such as
-        <link xlink:href="https://nixos.org/channels/nixos-21.05-small"><literal>nixos-21.05-small</literal></link>
+        <link xlink:href="https://nixos.org/channels/nixos-21.11-small"><literal>nixos-21.11-small</literal></link>
         or
         <link xlink:href="https://nixos.org/channels/nixos-unstable-small"><literal>nixos-unstable-small</literal></link>.
         These are identical to the stable and unstable channels
@@ -60,8 +60,8 @@
   <para>
     When you first install NixOS, you’re automatically subscribed to the
     NixOS channel that corresponds to your installation source. For
-    instance, if you installed from a 21.05 ISO, you will be subscribed
-    to the <literal>nixos-21.05</literal> channel. To see which NixOS
+    instance, if you installed from a 21.11 ISO, you will be subscribed
+    to the <literal>nixos-21.11</literal> channel. To see which NixOS
     channel you’re subscribed to, run the following as root:
   </para>
   <programlisting>
@@ -76,17 +76,17 @@ nixos https://nixos.org/channels/nixos-unstable
 </programlisting>
   <para>
     (Be sure to include the <literal>nixos</literal> parameter at the
-    end.) For instance, to use the NixOS 21.05 stable channel:
+    end.) For instance, to use the NixOS 21.11 stable channel:
   </para>
   <programlisting>
-# nix-channel --add https://nixos.org/channels/nixos-21.05 nixos
+# nix-channel --add https://nixos.org/channels/nixos-21.11 nixos
 </programlisting>
   <para>
     If you have a server, you may want to use the <quote>small</quote>
     channel instead:
   </para>
   <programlisting>
-# nix-channel --add https://nixos.org/channels/nixos-21.05-small nixos
+# nix-channel --add https://nixos.org/channels/nixos-21.11-small nixos
 </programlisting>
   <para>
     And if you want to live on the bleeding edge:
@@ -146,7 +146,7 @@ system.autoUpgrade.allowReboot = true;
       also specify a channel explicitly, e.g.
     </para>
     <programlisting language="bash">
-system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.05;
+system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.11;
 </programlisting>
   </section>
 </chapter>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index f3a5db5fb1c8..6b706e4aeaa1 100644
--- a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -1,9 +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>
-  <para>
-    In addition to numerous new and upgraded packages, this release has
-    the following highlights:
-  </para>
+  <title>Release 21.11 (“Porcupine”, 2021/11/30)</title>
   <itemizedlist spacing="compact">
     <listitem>
       <para>
@@ -14,15 +10,34 @@
   </itemizedlist>
   <section xml:id="sec-release-21.11-highlights">
     <title>Highlights</title>
+    <para>
+      In addition to numerous new and upgraded packages, this release
+      has the following highlights:
+    </para>
     <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>
       <listitem>
         <para>
-          kOps now defaults to 1.21.1, which uses containerd as the
+          kops now defaults to 1.21.1, which uses containerd as the
           default runtime.
         </para>
       </listitem>
@@ -46,13 +61,36 @@
       </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.
+          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, optionally, be run during a
+          <literal>nixos-rebuild dry-activate</literal> and can 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>
@@ -83,7 +121,8 @@
           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.
+          6</link> for more details. <literal>helmfile</literal> also
+          defaults to 0.141.0, which is the minimum compatible version.
         </para>
       </listitem>
       <listitem>
@@ -93,6 +132,81 @@
           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>
+      <listitem>
+        <para>
+          ORY Kratos was updated to version 0.8.0-alpha.3
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              This release requires you to run SQL migrations. Please,
+              as always, create a backup of your database first!
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The SDKs are now generated with tag v0alpha2 to reflect
+              that some signatures have changed in a breaking fashion.
+              Please update your imports from v0alpha1 to v0alpha2.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The SMTPS scheme used in courier config URL with
+              cleartext/StartTLS/TLS SMTP connection types is now only
+              supporting implicit TLS. For StartTLS and cleartext SMTP,
+              please use the SMTP scheme instead.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              for more details, see
+              <link xlink:href="https://github.com/ory/kratos/releases/tag/v0.8.0-alpha.1">Release
+              Notes</link>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-new-services">
@@ -131,16 +245,32 @@
       </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>.
+          <link xlink:href="options.html#opt-services.kea.dhcp4">services.kea</link>.
         </para>
       </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>.
+          <link xlink:href="options.html#opt-services.owncast.enable">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.enable">services.peertube</link>.
         </para>
       </listitem>
       <listitem>
@@ -356,6 +486,53 @@
           <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>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/prometheus-community/smartctl_exporter">smartctl_exporter</link>,
+          a Prometheus exporter for
+          <link xlink:href="https://en.wikipedia.org/wiki/S.M.A.R.T.">S.M.A.R.T.</link>
+          data. Available as
+          <link xlink:href="options.html#opt-services.prometheus.exporters.smartctl.enable">services.prometheus.exporters.smartctl</link>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-incompatibilities">
@@ -363,6 +540,24 @@
     <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>.
@@ -380,6 +575,14 @@
       </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
@@ -465,7 +668,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>
@@ -526,6 +729,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>
@@ -1128,6 +1342,84 @@ Superuser created successfully.
           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">
@@ -1151,25 +1443,31 @@ Superuser created successfully.
         <para>
           In NixOS virtual machines (QEMU), the
           <literal>virtualisation</literal> module has been updated with
-          new options to configure:
+          new options:
         </para>
         <itemizedlist spacing="compact">
           <listitem>
             <para>
-              IPv4 port forwarding
-              (<link xlink:href="options.html#opt-virtualisation.forwardPorts"><literal>virtualisation.forwardPorts</literal></link>),
+              <link xlink:href="options.html#opt-virtualisation.forwardPorts"><literal>forwardPorts</literal></link>
+              to configure IPv4 port forwarding,
             </para>
           </listitem>
           <listitem>
             <para>
-              shared host directories
-              (<link xlink:href="options.html#opt-virtualisation.sharedDirectories"><literal>virtualisation.sharedDirectories</literal></link>),
+              <link xlink:href="options.html#opt-virtualisation.sharedDirectories"><literal>sharedDirectories</literal></link>
+              to set up shared host directories,
             </para>
           </listitem>
           <listitem>
             <para>
-              screen resolution
-              (<link xlink:href="options.html#opt-virtualisation.resolution"><literal>virtualisation.resolution</literal></link>).
+              <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>
@@ -1425,6 +1723,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
@@ -1556,15 +1871,6 @@ Superuser created successfully.
       </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>
@@ -1595,6 +1901,143 @@ Superuser created successfully.
           </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>
+      <listitem>
+        <para>
+          hydrus has been upgraded from version <literal>438</literal>
+          to <literal>463</literal>. Since upgrading between releases
+          this old is advised against, be sure to have a backup of your
+          data before upgrading. For details, see
+          <link xlink:href="https://hydrusnetwork.github.io/hydrus/help/getting_started_installing.html#big_updates">the
+          hydrus manual</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          More jdk and jre versions are now exposed via
+          <literal>java-packages.compiler</literal>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
new file mode 100644
index 000000000000..4a2e86a60b92
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -0,0 +1,315 @@
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-22.05">
+  <title>Release 22.05 (“Quokka”, 2022.05/??)</title>
+  <para>
+    In addition to numerous new and upgraded packages, this release has
+    the following highlights:
+  </para>
+  <itemizedlist spacing="compact">
+    <listitem>
+      <para>
+        Support is planned until the end of December 2022, handing over
+        to 22.11.
+      </para>
+    </listitem>
+  </itemizedlist>
+  <section xml:id="sec-release-22.05-highlights">
+    <title>Highlights</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <literal>security.acme.defaults</literal> has been added to
+          simplify configuring settings for many certificates at once.
+          This also opens up the the option to use DNS-01 validation
+          when using <literal>enableACME</literal> on web server virtual
+          hosts (e.g.
+          <literal>services.nginx.virtualHosts.*.enableACME</literal>).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          PHP 8.1 is now available
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Mattermost has been updated to version 6.2. Migrations may
+          take a while, see the
+          <link xlink:href="https://docs.mattermost.com/install/self-managed-changelog.html#release-v6.2-feature-release">upgrade
+          notes</link>.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.05-new-services">
+    <title>New Services</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw">aesmd</link>,
+          the Intel SGX Architectural Enclave Service Manager. Available
+          as
+          <link linkend="opt-services.aesmd.enable">services.aesmd</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://docs.docker.com/engine/security/rootless/">rootless
+          Docker</link>, a <literal>systemd --user</literal> Docker
+          service which runs without root permissions. Available as
+          <link xlink:href="options.html#opt-virtualisation.docker.rootless.enable">virtualisation.docker.rootless.enable</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html">filebeat</link>,
+          a lightweight shipper for forwarding and centralizing log
+          data. Available as
+          <link linkend="opt-services.filebeat.enable">services.filebeat</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/ngoduykhanh/PowerDNS-Admin">PowerDNS-Admin</link>,
+          a web interface for the PowerDNS server. Available at
+          <link xlink:href="options.html#opt-services.powerdns-admin.enable">services.powerdns-admin</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://maddy.email">maddy</link>, a
+          composable all-in-one mail server. Available as
+          <link xlink:href="options.html#opt-services.maddy.enable">services.maddy</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://tetrd.app">tetrd</link>, share your
+          internet connection from your device to your PC and vice versa
+          through a USB cable. Available at
+          <link linkend="opt-services.tetrd.enable">services.tetrd</link>.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.05-incompatibilities">
+    <title>Backward Incompatibilities</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <literal>pkgs.ghc</literal> now refers to
+          <literal>pkgs.targetPackages.haskellPackages.ghc</literal>.
+          This <emphasis>only</emphasis> makes a difference if you are
+          cross-compiling and will ensure that
+          <literal>pkgs.ghc</literal> always runs on the host platform
+          and compiles for the target platform (similar to
+          <literal>pkgs.gcc</literal> for example).
+          <literal>haskellPackages.ghc</literal> still behaves as
+          before, running on the build platform and compiling for the
+          host platform (similar to <literal>stdenv.cc</literal>). This
+          means you don’t have to adjust your derivations if you use
+          <literal>haskellPackages.callPackage</literal>, but when using
+          <literal>pkgs.callPackage</literal> and taking
+          <literal>ghc</literal> as an input, you should now use
+          <literal>buildPackages.ghc</literal> instead to ensure cross
+          compilation keeps working (or switch to
+          <literal>haskellPackages.callPackage</literal>).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.emacsPackages.orgPackages</literal> is removed
+          because org elpa is deprecated. The packages in the top level
+          of <literal>pkgs.emacsPackages</literal>, such as org and
+          org-contrib, refer to the ones in
+          <literal>pkgs.emacsPackages.elpaPackages</literal> and
+          <literal>pkgs.emacsPackages.nongnuPackages</literal> where the
+          new versions will release.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>services.kubernetes.addons.dashboard</literal> was
+          removed due to it being an outdated version.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The MoinMoin wiki engine
+          (<literal>services.moinmoin</literal>) has been removed,
+          because Python 2 is being retired from nixpkgs.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>wafHook</literal> hook now honors
+          <literal>NIX_BUILD_CORES</literal> when
+          <literal>enableParallelBuilding</literal> is not set
+          explicitly. Packages can restore the old behaviour by setting
+          <literal>enableParallelBuilding=false</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.claws-mail-gtk2</literal>, representing Claws
+          Mail’s older release version three, was removed in order to
+          get rid of Python 2. Please switch to
+          <literal>claws-mail</literal>, which is Claws Mail’s latest
+          release based on GTK+3 and Python 3.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>writers.writePython2</literal> and corresponding
+          <literal>writers.writePython2Bin</literal> convenience
+          functions to create executable Python 2 scripts in the store
+          were removed in preparation of removal of the Python 2
+          interpreter. Scripts have to be converted to Python 3 for use
+          with <literal>writers.writePython3</literal> or
+          <literal>writers.writePyPy2</literal> needs to be used.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          If you previously used
+          <literal>/etc/docker/daemon.json</literal>, you need to
+          incorporate the changes into the new option
+          <literal>virtualisation.docker.daemon.settings</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>autorestic</literal> package has been upgraded
+          from 1.3.0 to 1.5.0 which introduces breaking changes in
+          config file, check
+          <link xlink:href="https://autorestic.vercel.app/migration/1.4_1.5">their
+          migration guide</link> for more details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          For <literal>pkgs.python3.pkgs.ipython</literal>, its direct
+          dependency
+          <literal>pkgs.python3.pkgs.matplotlib-inline</literal> (which
+          is really an adapter to integrate matplotlib in ipython if it
+          is installed) does not depend on
+          <literal>pkgs.python3.pkgs.matplotlib</literal> anymore. This
+          is closer to a non-Nix install of ipython. This has the added
+          benefit to reduce the closure size of
+          <literal>ipython</literal> from ~400MB to ~160MB (including
+          ~100MB for python itself).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>documentation.man</literal> has been refactored to
+          support choosing a man implementation other than GNU’s
+          <literal>man-db</literal>. For this,
+          <literal>documentation.man.manualPages</literal> has been
+          renamed to
+          <literal>documentation.man.man-db.manualPages</literal>. If
+          you want to use the new alternative man implementation
+          <literal>mandoc</literal>, add
+          <literal>documentation.man = { enable = true; man-db.enable = false; mandoc.enable = true; }</literal>
+          to your configuration.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.05-notable-changes">
+    <title>Other Notable Changes</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          The option
+          <link linkend="opt-services.redis.servers">services.redis.servers</link>
+          was added to support per-application
+          <literal>redis-server</literal> which is more secure since
+          Redis databases are only mere key prefixes without any
+          configuration or ACL of their own. Backward-compatibility is
+          preserved by mapping old
+          <literal>services.redis.settings</literal> to
+          <literal>services.redis.servers.&quot;&quot;.settings</literal>,
+          but you are strongly encouraged to name each
+          <literal>redis-server</literal> instance after the application
+          using it, instead of keeping that nameless one. Except for the
+          nameless
+          <literal>services.redis.servers.&quot;&quot;</literal> still
+          accessible at <literal>127.0.0.1:6379</literal>, and to the
+          members of the Unix group <literal>redis</literal> through the
+          Unix socket <literal>/run/redis/redis.sock</literal>, all
+          other <literal>services.redis.servers.${serverName}</literal>
+          are only accessible by default to the members of the Unix
+          group <literal>redis-${serverName}</literal> through the Unix
+          socket <literal>/run/redis-${serverName}/redis.sock</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <literal>writers.writePyPy2</literal>/<literal>writers.writePyPy3</literal>
+          and corresponding
+          <literal>writers.writePyPy2Bin</literal>/<literal>writers.writePyPy3Bin</literal>
+          convenience functions to create executable Python 2/3 scripts
+          using the PyPy interpreter were added.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>influxdb2</literal> package was split into
+          <literal>influxdb2-server</literal> and
+          <literal>influxdb2-cli</literal>, matching the split that took
+          place upstream. A combined <literal>influxdb2</literal>
+          package is still provided in this release for backwards
+          compatibilty, but will be removed at a later date.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.unifi.openPorts</literal> option default
+          value of <literal>true</literal> is now deprecated and will be
+          changed to <literal>false</literal> in 22.11. Configurations
+          using this default will print a warning when rebuilt.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>security.acme</literal> certificates will now
+          correctly check for CA revokation before reaching their
+          minimum age.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Removing domains from
+          <literal>security.acme.certs._name_.extraDomainNames</literal>
+          will now correctly remove those domains during rebuild/renew.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The option
+          <link linkend="opt-programs.ssh.enableAskPassword">programs.ssh.enableAskPassword</link>
+          was added, decoupling the setting of
+          <literal>SSH_ASKPASS</literal> from
+          <literal>services.xserver.enable</literal>. This allows easy
+          usage in non-X11 environments, e.g. Wayland.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.stubby</literal> module was converted to
+          a
+          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">settings-style</link>
+          configuration.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The option <literal>services.duplicati.dataDir</literal> has
+          been added to allow changing the location of duplicati’s
+          files.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+</section>
diff --git a/nixpkgs/nixos/doc/manual/installation/installing.chapter.md b/nixpkgs/nixos/doc/manual/installation/installing.chapter.md
index a0823b51e9cb..def4f37fbcaa 100644
--- a/nixpkgs/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixpkgs/nixos/doc/manual/installation/installing.chapter.md
@@ -15,7 +15,10 @@ 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.
+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
diff --git a/nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md b/nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md
index b7903b9d3cbb..faeefc4451dc 100644
--- a/nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md
+++ b/nixpkgs/nixos/doc/manual/installation/upgrading.chapter.md
@@ -6,7 +6,7 @@ expressions and associated binaries. The NixOS channels are updated
 automatically from NixOS's Git repository after certain tests have
 passed and all packages have been built. These channels are:
 
--   *Stable channels*, such as [`nixos-21.05`](https://nixos.org/channels/nixos-21.05).
+-   *Stable channels*, such as [`nixos-21.11`](https://nixos.org/channels/nixos-21.11).
     These only get conservative bug fixes and package upgrades. For
     instance, a channel update may cause the Linux kernel on your system
     to be upgraded from 4.19.34 to 4.19.38 (a minor bug fix), but not
@@ -19,7 +19,7 @@ passed and all packages have been built. These channels are:
     radical changes between channel updates. It's not recommended for
     production systems.
 
--   *Small channels*, such as [`nixos-21.05-small`](https://nixos.org/channels/nixos-21.05-small)
+-   *Small channels*, such as [`nixos-21.11-small`](https://nixos.org/channels/nixos-21.11-small)
     or [`nixos-unstable-small`](https://nixos.org/channels/nixos-unstable-small).
     These are identical to the stable and unstable channels described above,
     except that they contain fewer binary packages. This means they get updated
@@ -38,8 +38,8 @@ newest supported stable release.
 
 When you first install NixOS, you're automatically subscribed to the
 NixOS channel that corresponds to your installation source. For
-instance, if you installed from a 21.05 ISO, you will be subscribed to
-the `nixos-21.05` channel. To see which NixOS channel you're subscribed
+instance, if you installed from a 21.11 ISO, you will be subscribed to
+the `nixos-21.11` channel. To see which NixOS channel you're subscribed
 to, run the following as root:
 
 ```ShellSession
@@ -54,16 +54,16 @@ To switch to a different NixOS channel, do
 ```
 
 (Be sure to include the `nixos` parameter at the end.) For instance, to
-use the NixOS 21.05 stable channel:
+use the NixOS 21.11 stable channel:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-21.05 nixos
+# nix-channel --add https://nixos.org/channels/nixos-21.11 nixos
 ```
 
 If you have a server, you may want to use the "small" channel instead:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-21.05-small nixos
+# nix-channel --add https://nixos.org/channels/nixos-21.11-small nixos
 ```
 
 And if you want to live on the bleeding edge:
@@ -114,5 +114,5 @@ the new generation contains a different kernel, initrd or kernel
 modules. You can also specify a channel explicitly, e.g.
 
 ```nix
-system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.05;
+system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.11;
 ```
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-install.xml b/nixpkgs/nixos/doc/manual/man-nixos-install.xml
index 91542d37cbd5..eb6680b65677 100644
--- a/nixpkgs/nixos/doc/manual/man-nixos-install.xml
+++ b/nixpkgs/nixos/doc/manual/man-nixos-install.xml
@@ -69,9 +69,14 @@
    </arg>
 
    <arg>
-    <arg choice='plain'>
-     <option>--no-root-passwd</option>
-    </arg>
+    <group choice='req'>
+     <arg choice='plain'>
+      <option>--no-root-password</option>
+     </arg>
+     <arg choice='plain'>
+      <option>--no-root-passwd</option>
+     </arg>
+    </group>
    </arg>
 
    <arg>
@@ -157,7 +162,7 @@
     <listitem>
      <para>
       It prompts you for a password for the root account (unless
-      <option>--no-root-passwd</option> is specified).
+      <option>--no-root-password</option> is specified).
      </para>
     </listitem>
    </itemizedlist>
diff --git a/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml b/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml
index 0e0ea5d74b0b..6c7fc57f8d83 100644
--- a/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixpkgs/nixos/doc/manual/man-nixos-rebuild.xml
@@ -535,12 +535,8 @@
      </para>
 
      <para>
-      If <option>--build-host</option> is not explicitly specified,
-      <option>--build-host</option> will implicitly be set to the same value as
-      <option>--target-host</option>. So, if you only specify
-      <option>--target-host</option> both building and activation will take
-      place remotely (and no build artifacts will be copied to the local
-      machine).
+      If <option>--build-host</option> is not explicitly specified, building
+      will take place locally.
      </para>
 
      <para>
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md
index c1958b3a6bd4..48adc4ad33cb 100644
--- a/nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -1,14 +1,18 @@
-# Release 21.11 (“?”, 2021.11/??) {#sec-release-21.11}
-
-In addition to numerous new and upgraded packages, this release has the following highlights:
+# Release 21.11 (“Porcupine”, 2021/11/30) {#sec-release-21.11}
 
 - Support is planned until the end of June 2022, handing over to 22.05.
 
 ## Highlights {#sec-release-21.11-highlights}
 
+In addition to numerous new and upgraded packages, this release has the following 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.
+- kops now defaults to 1.21.1, which uses containerd as the default runtime.
 
 - `python3` now defaults to Python 3.9, updated from Python 3.8.
 
@@ -16,7 +20,12 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - 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.
 
-- Activation scripts can now opt int to be run when running `nixos-rebuild dry-activate` and detect the dry activation by reading `$NIXOS_ACTION`.
+- 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, optionally, be run during a `nixos-rebuild dry-activate` and can 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.
 
@@ -29,9 +38,24 @@ In addition to numerous new and upgraded packages, this release has the followin
 - 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
+
+- ORY Kratos was updated to version 0.8.0-alpha.3
+  - This release requires you to run SQL migrations. Please, as always, create a backup of your database first!
+  - The SDKs are now generated with tag v0alpha2 to reflect that some signatures have changed in a breaking fashion. Please update your imports from v0alpha1 to v0alpha2.
+  - The SMTPS scheme used in courier config URL with cleartext/StartTLS/TLS SMTP connection types is now only supporting implicit TLS. For StartTLS and cleartext SMTP, please use the SMTP scheme instead.
+  - for more details, see [Release Notes](https://github.com/ory/kratos/releases/tag/v0.8.0-alpha.1).
+
 ## 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).
@@ -42,9 +66,13 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [geoipupdate](https://github.com/maxmind/geoipupdate), a GeoIP database updater from MaxMind. Available as [services.geoipupdate](options.html#opt-services.geoipupdate.enable).
 
-- [Kea](https://www.isc.org/kea/), ISCs 2nd generation DHCP and DDNS server suite. Available at [services.kea](options.html#opt-services.kea).
+- [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.dhcp4).
 
-- [owncast](https://owncast.online/), self-hosted video live streaming solution. Available at [services.owncast](options.html#opt-services.owncast).
+- [owncast](https://owncast.online/), self-hosted video live streaming solution. Available at [services.owncast](options.html#opt-services.owncast.enable).
+
+- [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.enable).
 
 - [sourcehut](https://sr.ht), a collection of tools useful for software development. Available as [services.sourcehut](options.html#opt-services.sourcehut.enable).
 
@@ -109,13 +137,31 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [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).
+
+- [smartctl_exporter](https://github.com/prometheus-community/smartctl_exporter), a Prometheus exporter for [S.M.A.R.T.](https://en.wikipedia.org/wiki/S.M.A.R.T.) data. Available as [services.prometheus.exporters.smartctl](options.html#opt-services.prometheus.exporters.smartctl.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
   archived](https://github.com/the-paperless-project/paperless/commit/9b0063c9731f7c5f65b1852cb8caff97f5e40ba4)
@@ -166,7 +212,7 @@ In addition to numerous new and upgraded packages, this release has the followin
     Superuser created successfully.
     ```
 
-- The `staticjinja` package has been upgraded from 1.0.4 to 4.1.0
+- 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.
 
@@ -195,6 +241,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `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;`.
@@ -348,16 +396,38 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `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 to configure:
-    - IPv4 port forwarding ([`virtualisation.forwardPorts`](options.html#opt-virtualisation.forwardPorts)),
-    - shared host directories ([`virtualisation.sharedDirectories`](options.html#opt-virtualisation.sharedDirectories)),
-    - screen resolution ([`virtualisation.resolution`](options.html#opt-virtualisation.resolution)).
+- 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.
 
@@ -419,6 +489,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - 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.
@@ -450,9 +522,34 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `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.
+
+- hydrus has been upgraded from version `438` to `463`. Since upgrading between releases this old is advised against, be sure to have a backup of your data before upgrading. For details, see [the hydrus manual](https://hydrusnetwork.github.io/hydrus/help/getting_started_installing.html#big_updates).
+
+- More jdk and jre versions are now exposed via `java-packages.compiler`.
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md
new file mode 100644
index 000000000000..2a062d0573b5
--- /dev/null
+++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -0,0 +1,118 @@
+# Release 22.05 (“Quokka”, 2022.05/??) {#sec-release-22.05}
+
+In addition to numerous new and upgraded packages, this release has the following highlights:
+
+- Support is planned until the end of December 2022, handing over to 22.11.
+
+## Highlights {#sec-release-22.05-highlights}
+
+- `security.acme.defaults` has been added to simplify configuring
+  settings for many certificates at once. This also opens up the
+  the option to use DNS-01 validation when using `enableACME` on
+  web server virtual hosts (e.g. `services.nginx.virtualHosts.*.enableACME`).
+
+- PHP 8.1 is now available
+
+- Mattermost has been updated to version 6.2. Migrations may take a while,
+  see the [upgrade notes](https://docs.mattermost.com/install/self-managed-changelog.html#release-v6.2-feature-release).
+
+## New Services {#sec-release-22.05-new-services}
+
+- [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable).
+- [rootless Docker](https://docs.docker.com/engine/security/rootless/), a `systemd --user` Docker service which runs without root permissions. Available as [virtualisation.docker.rootless.enable](options.html#opt-virtualisation.docker.rootless.enable).
+
+- [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html), a lightweight shipper for forwarding and centralizing log data. Available as [services.filebeat](#opt-services.filebeat.enable).
+
+- [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](options.html#opt-services.powerdns-admin.enable).
+
+- [maddy](https://maddy.email), a composable all-in-one mail server. Available as [services.maddy](options.html#opt-services.maddy.enable).
+
+- [tetrd](https://tetrd.app), share your internet connection from your device to your PC and vice versa through a USB cable. Available at [services.tetrd](#opt-services.tetrd.enable).
+
+## Backward Incompatibilities {#sec-release-22.05-incompatibilities}
+
+- `pkgs.ghc` now refers to `pkgs.targetPackages.haskellPackages.ghc`.
+  This *only* makes a difference if you are cross-compiling and will
+  ensure that `pkgs.ghc` always runs on the host platform and compiles
+  for the target platform (similar to `pkgs.gcc` for example).
+  `haskellPackages.ghc` still behaves as before, running on the build
+  platform and compiling for the host platform (similar to `stdenv.cc`).
+  This means you don't have to adjust your derivations if you use
+  `haskellPackages.callPackage`, but when using `pkgs.callPackage` and
+  taking `ghc` as an input, you should now use `buildPackages.ghc`
+  instead to ensure cross compilation keeps working (or switch to
+  `haskellPackages.callPackage`).
+
+- `pkgs.emacsPackages.orgPackages` is removed because org elpa is deprecated.
+  The packages in the top level of `pkgs.emacsPackages`, such as org and
+  org-contrib, refer to the ones in `pkgs.emacsPackages.elpaPackages` and
+  `pkgs.emacsPackages.nongnuPackages` where the new versions will release.
+
+- `services.kubernetes.addons.dashboard` was removed due to it being an outdated version.
+
+- The MoinMoin wiki engine (`services.moinmoin`) has been removed, because Python 2 is being retired from nixpkgs.
+
+- The `wafHook` hook now honors `NIX_BUILD_CORES` when `enableParallelBuilding` is not set explicitly. Packages can restore the old behaviour by setting `enableParallelBuilding=false`.
+
+- `pkgs.claws-mail-gtk2`, representing Claws Mail's older release version three, was removed in order to get rid of Python 2.
+  Please switch to `claws-mail`, which is Claws Mail's latest release based on GTK+3 and Python 3.
+
+- The `writers.writePython2` and corresponding `writers.writePython2Bin` convenience functions to create executable Python 2 scripts in the store were removed in preparation of removal of the Python 2 interpreter.
+  Scripts have to be converted to Python 3 for use with `writers.writePython3` or `writers.writePyPy2` needs to be used.
+
+- If you previously used `/etc/docker/daemon.json`, you need to incorporate the changes into the new option `virtualisation.docker.daemon.settings`.
+
+- The `autorestic` package has been upgraded from 1.3.0 to 1.5.0 which introduces breaking changes in config file, check [their migration guide](https://autorestic.vercel.app/migration/1.4_1.5) for more details.
+
+- For `pkgs.python3.pkgs.ipython`, its direct dependency `pkgs.python3.pkgs.matplotlib-inline`
+  (which is really an adapter to integrate matplotlib in ipython if it is installed) does
+  not depend on `pkgs.python3.pkgs.matplotlib` anymore.
+  This is closer to a non-Nix install of ipython.
+  This has the added benefit to reduce the closure size of `ipython` from ~400MB to ~160MB
+  (including ~100MB for python itself).
+
+- `documentation.man` has been refactored to support choosing a man implementation other than GNU's `man-db`. For this, `documentation.man.manualPages` has been renamed to `documentation.man.man-db.manualPages`. If you want to use the new alternative man implementation `mandoc`, add `documentation.man = { enable = true; man-db.enable = false; mandoc.enable = true; }` to your configuration.
+
+## Other Notable Changes {#sec-release-22.05-notable-changes}
+
+- The option [services.redis.servers](#opt-services.redis.servers) was added
+  to support per-application `redis-server` which is more secure since Redis databases
+  are only mere key prefixes without any configuration or ACL of their own.
+  Backward-compatibility is preserved by mapping old `services.redis.settings`
+  to `services.redis.servers."".settings`, but you are strongly encouraged
+  to name each `redis-server` instance after the application using it,
+  instead of keeping that nameless one.
+  Except for the nameless `services.redis.servers.""`
+  still accessible at `127.0.0.1:6379`,
+  and to the members of the Unix group `redis`
+  through the Unix socket `/run/redis/redis.sock`,
+  all other `services.redis.servers.${serverName}`
+  are only accessible by default
+  to the members of the Unix group `redis-${serverName}`
+  through the Unix socket `/run/redis-${serverName}/redis.sock`.
+
+- The `writers.writePyPy2`/`writers.writePyPy3` and corresponding `writers.writePyPy2Bin`/`writers.writePyPy3Bin` convenience functions to create executable Python 2/3 scripts using the PyPy interpreter were added.
+
+- The `influxdb2` package was split into `influxdb2-server` and
+  `influxdb2-cli`, matching the split that took place upstream. A
+  combined `influxdb2` package is still provided in this release for
+  backwards compatibilty, but will be removed at a later date.
+
+- The `services.unifi.openPorts` option default value of `true` is now deprecated and will be changed to `false` in 22.11.
+  Configurations using this default will print a warning when rebuilt.
+
+- `security.acme` certificates will now correctly check for CA
+  revokation before reaching their minimum age.
+
+- Removing domains from `security.acme.certs._name_.extraDomainNames`
+  will now correctly remove those domains during rebuild/renew.
+
+- The option
+  [programs.ssh.enableAskPassword](#opt-programs.ssh.enableAskPassword) was
+  added, decoupling the setting of `SSH_ASKPASS` from
+  `services.xserver.enable`. This allows easy usage in non-X11 environments,
+  e.g. Wayland.
+
+- The `services.stubby` module was converted to a [settings-style](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) configuration.
+
+- The option `services.duplicati.dataDir` has been added to allow changing the location of duplicati's files.
diff --git a/nixpkgs/nixos/lib/eval-config.nix b/nixpkgs/nixos/lib/eval-config.nix
index 15429a7160c5..62d09b8173bd 100644
--- a/nixpkgs/nixos/lib/eval-config.nix
+++ b/nixpkgs/nixos/lib/eval-config.nix
@@ -8,6 +8,7 @@
 # as subcomponents (e.g. the container feature, or nixops if network
 # expressions are ever made modular at the top level) can just use
 # types.submodule instead of using eval-config.nix
+evalConfigArgs@
 { # !!! system can be set modularly, would be nice to remove
   system ? builtins.currentSystem
 , # !!! is this argument needed any more? The pkgs argument can
@@ -28,7 +29,7 @@
                  in if e == "" then [] else [(import e)]
 }:
 
-let extraArgs_ = extraArgs; pkgs_ = pkgs;
+let pkgs_ = pkgs;
 in
 
 let
@@ -51,23 +52,49 @@ let
     };
   };
 
-in rec {
+  withWarnings = x:
+    lib.warnIf (evalConfigArgs?extraArgs) "The extraArgs argument to eval-config.nix is deprecated. Please set config._module.args instead."
+    lib.warnIf (evalConfigArgs?check) "The check argument to eval-config.nix is deprecated. Please set config._module.check instead."
+    x;
 
-  # Merge the option definitions in all modules, forming the full
-  # system configuration.
-  inherit (lib.evalModules {
-    inherit prefix check;
-    modules = baseModules ++ extraModules ++ [ pkgsModule ] ++ modules;
-    args = extraArgs;
+  legacyModules =
+    lib.optional (evalConfigArgs?extraArgs) {
+      config = {
+        _module.args = extraArgs;
+      };
+    }
+    ++ lib.optional (evalConfigArgs?check) {
+      config = {
+        _module.check = lib.mkDefault check;
+      };
+    };
+  allUserModules = modules ++ legacyModules;
+
+  noUserModules = lib.evalModules ({
+    inherit prefix;
+    modules = baseModules ++ extraModules ++ [ pkgsModule modulesModule ];
     specialArgs =
       { modulesPath = builtins.toString ../modules; } // specialArgs;
-  }) config options _module;
+  });
 
-  # These are the extra arguments passed to every module.  In
-  # particular, Nixpkgs is passed through the "pkgs" argument.
-  extraArgs = extraArgs_ // {
-    inherit baseModules extraModules modules;
+  # Extra arguments that are useful for constructing a similar configuration.
+  modulesModule = {
+    config = {
+      _module.args = {
+        inherit noUserModules baseModules extraModules modules;
+      };
+    };
   };
 
-  inherit (_module.args) pkgs;
+  nixosWithUserModules = noUserModules.extendModules { modules = allUserModules; };
+
+in withWarnings {
+
+  # Merge the option definitions in all modules, forming the full
+  # system configuration.
+  inherit (nixosWithUserModules) config options _module type;
+
+  inherit extraArgs;
+
+  inherit (nixosWithUserModules._module.args) pkgs;
 }
diff --git a/nixpkgs/nixos/lib/make-disk-image.nix b/nixpkgs/nixos/lib/make-disk-image.nix
index 55643facea03..15302ae82414 100644
--- a/nixpkgs/nixos/lib/make-disk-image.nix
+++ b/nixpkgs/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 --extra-experimental-features nix-command 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/nixpkgs/nixos/lib/make-options-doc/default.nix b/nixpkgs/nixos/lib/make-options-doc/default.nix
index e058e70f3888..44bc25be9238 100644
--- a/nixpkgs/nixos/lib/make-options-doc/default.nix
+++ b/nixpkgs/nixos/lib/make-options-doc/default.nix
@@ -24,18 +24,25 @@
 }:
 
 let
-  # Replace functions by the string <function>
-  substFunction = x:
-    if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x
-    else if builtins.isList x then map substFunction x
+  # Make a value safe for JSON. Functions are replaced by the string "<function>",
+  # derivations are replaced with an attrset
+  # { _type = "derivation"; name = <name of that derivation>; }.
+  # We need to handle derivations specially because consumers want to know about them,
+  # but we can't easily use the type,name subset of keys (since type is often used as
+  # a module option and might cause confusion). Use _type,name instead to the same
+  # effect, since _type is already used by the module system.
+  substSpecial = x:
+    if lib.isDerivation x then { _type = "derivation"; name = x.name; }
+    else if builtins.isAttrs x then lib.mapAttrs (name: substSpecial) x
+    else if builtins.isList x then map substSpecial x
     else if lib.isFunction x then "<function>"
     else x;
 
-  optionsListDesc = lib.flip map optionsListVisible
+  optionsList = lib.flip map optionsListVisible
    (opt: transformOptions opt
-    // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; }
-    // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; }
-    // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; }
+    // lib.optionalAttrs (opt ? example) { example = substSpecial opt.example; }
+    // lib.optionalAttrs (opt ? default) { default = substSpecial opt.default; }
+    // lib.optionalAttrs (opt ? type) { type = substSpecial opt.type; }
     // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages opt.name; }
    );
 
@@ -69,96 +76,25 @@ let
         + "</listitem>";
     in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>";
 
-  # Custom "less" that pushes up all the things ending in ".enable*"
-  # and ".package*"
-  optionLess = a: b:
-    let
-      ise = lib.hasPrefix "enable";
-      isp = lib.hasPrefix "package";
-      cmp = lib.splitByAndCompare ise lib.compare
-                                 (lib.splitByAndCompare isp lib.compare lib.compare);
-    in lib.compareLists cmp a.loc b.loc < 0;
-
   # Remove invisible and internal options.
   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.
-  # 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);
 
-  # TODO: declarations: link to github
-  singleAsciiDoc = name: value: ''
-    == ${name}
-
-    ${value.description}
-
-    [discrete]
-    === details
-
-    Type:: ${value.type}
-    ${ if lib.hasAttr "default" value
-       then ''
-        Default::
-        +
-        ----
-        ${builtins.toJSON value.default}
-        ----
-      ''
-      else "No Default:: {blank}"
-    }
-    ${ if value.readOnly
-       then "Read Only:: {blank}"
-      else ""
-    }
-    ${ if lib.hasAttr "example" value
-       then ''
-        Example::
-        +
-        ----
-        ${builtins.toJSON value.example}
-        ----
-      ''
-      else "No Example:: {blank}"
-    }
-  '';
-
-  singleMDDoc = name: value: ''
-    ## ${lib.escape [ "<" ">" ] name}
-    ${value.description}
-
-    ${lib.optionalString (value ? type) ''
-      *_Type_*:
-      ${value.type}
-    ''}
-
-    ${lib.optionalString (value ? default) ''
-      *_Default_*
-      ```
-      ${builtins.toJSON value.default}
-      ```
-    ''}
-
-    ${lib.optionalString (value ? example) ''
-      *_Example_*
-      ```
-      ${builtins.toJSON value.example}
-      ```
-    ''}
-  '';
-
-in {
+in rec {
   inherit optionsNix;
 
-  optionsAsciiDoc = lib.concatStringsSep "\n" (lib.mapAttrsToList singleAsciiDoc optionsNix);
+  optionsAsciiDoc = pkgs.runCommand "options.adoc" {} ''
+    ${pkgs.python3Minimal}/bin/python ${./generateAsciiDoc.py} \
+      < ${optionsJSON}/share/doc/nixos/options.json \
+      > $out
+  '';
 
-  optionsMDDoc = lib.concatStringsSep "\n" (lib.mapAttrsToList singleMDDoc optionsNix);
+  optionsCommonMark = pkgs.runCommand "options.md" {} ''
+    ${pkgs.python3Minimal}/bin/python ${./generateCommonMark.py} \
+      < ${optionsJSON}/share/doc/nixos/options.json \
+      > $out
+  '';
 
   optionsJSON = pkgs.runCommand "options.json"
     { meta.description = "List of NixOS options in JSON format";
@@ -176,7 +112,19 @@ in {
       mkdir -p $out/nix-support
       echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products
       echo "file json-br $dst/options.json.br" >> $out/nix-support/hydra-build-products
-    ''; # */
+    '';
+
+  # Convert options.json into an XML file.
+  # The actual generation of the xml file is done in nix purely for the convenience
+  # of not having to generate the xml some other way
+  optionsXML = pkgs.runCommand "options.xml" {} ''
+    export NIX_STORE_DIR=$TMPDIR/store
+    export NIX_STATE_DIR=$TMPDIR/state
+    ${pkgs.nix}/bin/nix-instantiate \
+      --eval --xml --strict ${./optionsJSONtoXML.nix} \
+      --argstr file ${optionsJSON}/share/doc/nixos/options.json \
+      > "$out"
+  '';
 
   optionsDocBook = pkgs.runCommand "options-docbook.xml" {} ''
     optionsXML=${optionsXML}
diff --git a/nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py b/nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py
new file mode 100644
index 000000000000..48eadd248c5a
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-options-doc/generateAsciiDoc.py
@@ -0,0 +1,37 @@
+import json
+import sys
+
+options = json.load(sys.stdin)
+# TODO: declarations: link to github
+for (name, value) in options.items():
+    print(f'== {name}')
+    print()
+    print(value['description'])
+    print()
+    print('[discrete]')
+    print('=== details')
+    print()
+    print(f'Type:: {value["type"]}')
+    if 'default' in value:
+        print('Default::')
+        print('+')
+        print('----')
+        print(json.dumps(value['default'], ensure_ascii=False, separators=(',', ':')))
+        print('----')
+        print()
+    else:
+        print('No Default:: {blank}')
+    if value['readOnly']:
+        print('Read Only:: {blank}')
+    else:
+        print()
+    if 'example' in value:
+        print('Example::')
+        print('+')
+        print('----')
+        print(json.dumps(value['example'], ensure_ascii=False, separators=(',', ':')))
+        print('----')
+        print()
+    else:
+        print('No Example:: {blank}')
+    print()
diff --git a/nixpkgs/nixos/lib/make-options-doc/generateCommonMark.py b/nixpkgs/nixos/lib/make-options-doc/generateCommonMark.py
new file mode 100644
index 000000000000..404e53b0df9c
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-options-doc/generateCommonMark.py
@@ -0,0 +1,27 @@
+import json
+import sys
+
+options = json.load(sys.stdin)
+for (name, value) in options.items():
+    print('##', name.replace('<', '\\<').replace('>', '\\>'))
+    print(value['description'])
+    print()
+    if 'type' in value:
+        print('*_Type_*:')
+        print(value['type'])
+        print()
+    print()
+    if 'default' in value:
+        print('*_Default_*')
+        print('```')
+        print(json.dumps(value['default'], ensure_ascii=False, separators=(',', ':')))
+        print('```')
+    print()
+    print()
+    if 'example' in value:
+        print('*_Example_*')
+        print('```')
+        print(json.dumps(value['example'], ensure_ascii=False, separators=(',', ':')))
+        print('```')
+    print()
+    print()
diff --git a/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
index da4cd164bf20..b286f7b5e2c0 100644
--- a/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
+++ b/nixpkgs/nixos/lib/make-options-doc/options-to-docbook.xsl
@@ -20,7 +20,7 @@
       <title>Configuration Options</title>
       <variablelist xml:id="configuration-variable-list">
         <xsl:for-each select="attrs">
-          <xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '&lt;', '_'), '>', '_'))" />
+          <xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '&lt;', '_'), '>', '_'), ':', '_'))" />
           <varlistentry>
             <term xlink:href="#{$id}">
               <xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute>
@@ -189,7 +189,7 @@
   </xsl:template>
 
 
-  <xsl:template match="derivation">
+  <xsl:template match="attrs[attr[@name = '_type' and string[@value = 'derivation']]]">
     <replaceable>(build of <xsl:value-of select="attr[@name = 'name']/string/@value" />)</replaceable>
   </xsl:template>
 
diff --git a/nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix b/nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix
new file mode 100644
index 000000000000..ba50c5f898b5
--- /dev/null
+++ b/nixpkgs/nixos/lib/make-options-doc/optionsJSONtoXML.nix
@@ -0,0 +1,6 @@
+{ file }:
+
+builtins.attrValues
+  (builtins.mapAttrs
+    (name: def: def // { inherit name; })
+    (builtins.fromJSON (builtins.readFile file)))
diff --git a/nixpkgs/nixos/lib/make-options-doc/sortXML.py b/nixpkgs/nixos/lib/make-options-doc/sortXML.py
index 717820788c94..e63ff3538b3f 100644
--- a/nixpkgs/nixos/lib/make-options-doc/sortXML.py
+++ b/nixpkgs/nixos/lib/make-options-doc/sortXML.py
@@ -19,7 +19,6 @@ def sortKey(opt):
         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")
diff --git a/nixpkgs/nixos/lib/make-squashfs.nix b/nixpkgs/nixos/lib/make-squashfs.nix
index 8690c42e7ac9..170d315fb751 100644
--- a/nixpkgs/nixos/lib/make-squashfs.nix
+++ b/nixpkgs/nixos/lib/make-squashfs.nix
@@ -21,8 +21,15 @@ stdenv.mkDerivation {
       # for nix-store --load-db.
       cp $closureInfo/registration nix-path-registration
 
+      # 64 cores on i686 does not work
+      # fails with FATAL ERROR: mangle2:: xz compress failed with error code 5
+      if ((NIX_BUILD_CORES > 48)); then
+        NIX_BUILD_CORES=48
+      fi
+
       # Generate the squashfs image.
       mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out \
-        -no-hardlinks -keep-as-directory -all-root -b 1048576 -comp ${comp}
+        -no-hardlinks -keep-as-directory -all-root -b 1048576 -comp ${comp} \
+        -processors $NIX_BUILD_CORES
     '';
 }
diff --git a/nixpkgs/nixos/lib/make-zfs-image.nix b/nixpkgs/nixos/lib/make-zfs-image.nix
index 40648ca24d4d..a84732aa1171 100644
--- a/nixpkgs/nixos/lib/make-zfs-image.nix
+++ b/nixpkgs/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/nixpkgs/nixos/lib/qemu-common.nix b/nixpkgs/nixos/lib/qemu-common.nix
index 84f9060acd63..f3af85040bd6 100644
--- a/nixpkgs/nixos/lib/qemu-common.nix
+++ b/nixpkgs/nixos/lib/qemu-common.nix
@@ -17,12 +17,12 @@ rec {
       ''-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}'";
 
   qemuBinary = qemuPkg: {
-    x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu max";
+    x86_64-linux = "${qemuPkg}/bin/qemu-kvm -cpu qemu64";
     armv7l-linux = "${qemuPkg}/bin/qemu-system-arm -enable-kvm -machine virt -cpu host";
     aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -enable-kvm -machine virt,gic-version=host -cpu host";
     powerpc64le-linux = "${qemuPkg}/bin/qemu-system-ppc64 -machine powernv";
diff --git a/nixpkgs/nixos/modules/system/boot/systemd-lib.nix b/nixpkgs/nixos/lib/systemd-lib.nix
index 2dbf15031a08..6c4d27018eed 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd-lib.nix
+++ b/nixpkgs/nixos/lib/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/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix b/nixpkgs/nixos/lib/systemd-unit-options.nix
index 4154389b2ce5..01f954a4d3e0 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixpkgs/nixos/lib/systemd-unit-options.nix
@@ -1,7 +1,7 @@
-{ config, lib }:
+{ lib, systemdUtils }:
 
+with systemdUtils.lib;
 with lib;
-with import ./systemd-lib.nix { inherit config lib pkgs; };
 
 let
   checkService = checkUnitConfig "Service" [
diff --git a/nixpkgs/nixos/lib/test-driver/default.nix b/nixpkgs/nixos/lib/test-driver/default.nix
new file mode 100644
index 000000000000..3f63bc705b90
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/default.nix
@@ -0,0 +1,32 @@
+{ lib
+, python3Packages
+, enableOCR ? false
+, qemu_pkg ? qemu_test
+, coreutils
+, imagemagick_light
+, libtiff
+, netpbm
+, qemu_test
+, socat
+, tesseract4
+, vde2
+}:
+
+python3Packages.buildPythonApplication rec {
+  pname = "nixos-test-driver";
+  version = "1.0";
+  src = ./.;
+
+  propagatedBuildInputs = [ coreutils netpbm python3Packages.colorama python3Packages.ptpython qemu_pkg socat vde2 ]
+    ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ]);
+
+  doCheck = true;
+  checkInputs = with python3Packages; [ mypy pylint black ];
+  checkPhase = ''
+    mypy --disallow-untyped-defs \
+          --no-implicit-optional \
+          --ignore-missing-imports ${src}/test_driver
+    pylint --errors-only ${src}/test_driver
+    black --check --diff ${src}/test_driver
+  '';
+}
diff --git a/nixpkgs/nixos/lib/test-driver/setup.py b/nixpkgs/nixos/lib/test-driver/setup.py
new file mode 100644
index 000000000000..156995472169
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+  name="nixos-test-driver",
+  version='1.0',
+  packages=find_packages(),
+  entry_points={
+    "console_scripts": [
+      "nixos-test-driver=test_driver:main",
+      "generate-driver-symbols=test_driver:generate_driver_symbols"
+    ]
+  },
+)
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py b/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py
new file mode 100755
index 000000000000..5477ab5cd038
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/__init__.py
@@ -0,0 +1,100 @@
+from pathlib import Path
+import argparse
+import ptpython.repl
+import os
+import time
+
+from test_driver.logger import rootlog
+from test_driver.driver import Driver
+
+
+class EnvDefault(argparse.Action):
+    """An argpars Action that takes values from the specified
+    environment variable as the flags default value.
+    """
+
+    def __init__(self, envvar, required=False, default=None, nargs=None, **kwargs):  # type: ignore
+        if not default and envvar:
+            if envvar in os.environ:
+                if nargs is not None and (nargs.isdigit() or nargs in ["*", "+"]):
+                    default = os.environ[envvar].split()
+                else:
+                    default = os.environ[envvar]
+                kwargs["help"] = (
+                    kwargs["help"] + f" (default from environment: {default})"
+                )
+        if required and default:
+            required = False
+        super(EnvDefault, self).__init__(
+            default=default, required=required, nargs=nargs, **kwargs
+        )
+
+    def __call__(self, parser, namespace, values, option_string=None):  # type: ignore
+        setattr(namespace, self.dest, values)
+
+
+def main() -> None:
+    arg_parser = argparse.ArgumentParser(prog="nixos-test-driver")
+    arg_parser.add_argument(
+        "-K",
+        "--keep-vm-state",
+        help="re-use a VM state coming from a previous run",
+        action="store_true",
+    )
+    arg_parser.add_argument(
+        "-I",
+        "--interactive",
+        help="drop into a python repl and run the tests interactively",
+        action="store_true",
+    )
+    arg_parser.add_argument(
+        "--start-scripts",
+        metavar="START-SCRIPT",
+        action=EnvDefault,
+        envvar="startScripts",
+        nargs="*",
+        help="start scripts for participating virtual machines",
+    )
+    arg_parser.add_argument(
+        "--vlans",
+        metavar="VLAN",
+        action=EnvDefault,
+        envvar="vlans",
+        nargs="*",
+        help="vlans to span by the driver",
+    )
+    arg_parser.add_argument(
+        "testscript",
+        action=EnvDefault,
+        envvar="testScript",
+        help="the test script to run",
+        type=Path,
+    )
+
+    args = arg_parser.parse_args()
+
+    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")
+
+
+def generate_driver_symbols() -> None:
+    """
+    This generates a file with symbols of the test-driver code that can be used
+    in user's test scripts. That list is then used by pyflakes to lint those
+    scripts.
+    """
+    d = Driver([], [], "")
+    test_symbols = d.test_symbols()
+    with open("driver-symbols", "w") as fp:
+        fp.write(",".join(test_symbols.keys()))
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/driver.py b/nixpkgs/nixos/lib/test-driver/test_driver/driver.py
new file mode 100644
index 000000000000..f3af98537ad6
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/driver.py
@@ -0,0 +1,161 @@
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Any, Dict, Iterator, List
+import os
+import tempfile
+
+from test_driver.logger import rootlog
+from test_driver.machine import Machine, NixStartScript, retry
+from test_driver.vlan import VLan
+
+
+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 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)
+
+        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(self) -> None:
+        rootlog._print_serial_logs = False
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/logger.py b/nixpkgs/nixos/lib/test-driver/test_driver/logger.py
new file mode 100644
index 000000000000..5b3091a5129c
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/logger.py
@@ -0,0 +1,101 @@
+from colorama import Style
+from contextlib import contextmanager
+from typing import Any, Dict, Iterator
+from queue import Queue, Empty
+from xml.sax.saxutils import XMLGenerator
+import codecs
+import os
+import sys
+import time
+import unicodedata
+
+
+class Logger:
+    def __init__(self) -> None:
+        self.logfile = os.environ.get("LOGFILE", "/dev/null")
+        self.logfile_handle = codecs.open(self.logfile, "wb")
+        self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8")
+        self.queue: "Queue[Dict[str, str]]" = Queue()
+
+        self.xml.startDocument()
+        self.xml.startElement("logfile", attrs={})
+
+        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()
+        self.logfile_handle.close()
+
+    def sanitise(self, message: str) -> str:
+        return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C")
+
+    def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
+        if "machine" in attributes:
+            return "{}: {}".format(attributes["machine"], message)
+        return message
+
+    def log_line(self, message: str, attributes: Dict[str, str]) -> None:
+        self.xml.startElement("line", attributes)
+        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:
+        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:
+            self._eprint(
+                Style.DIM + "{} # {}".format(machine, message) + Style.RESET_ALL
+            )
+
+    def enqueue(self, item: Dict[str, str]) -> None:
+        self.queue.put(item)
+
+    def drain_log_queue(self) -> None:
+        try:
+            while True:
+                item = self.queue.get_nowait()
+                msg = self.sanitise(item["msg"])
+                del item["msg"]
+                self.log_line(msg, item)
+        except Empty:
+            pass
+
+    @contextmanager
+    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
+        self._eprint(self.maybe_prefix(message, attributes))
+
+        self.xml.startElement("nest", attrs={})
+        self.xml.startElement("head", attributes)
+        self.xml.characters(message)
+        self.xml.endElement("head")
+
+        tic = time.time()
+        self.drain_log_queue()
+        yield
+        self.drain_log_queue()
+        toc = time.time()
+        self.log("(finished: {}, in {:.2f} seconds)".format(message, toc - tic))
+
+        self.xml.endElement("nest")
+
+
+rootlog = Logger()
diff --git a/nixpkgs/nixos/lib/test-driver/test-driver.py b/nixpkgs/nixos/lib/test-driver/test_driver/machine.py
index e659b0c04f50..b3dbe5126fcc 100755..100644
--- a/nixpkgs/nixos/lib/test-driver/test-driver.py
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/machine.py
@@ -1,20 +1,11 @@
-#! /somewhere/python3
-from contextlib import contextmanager, _GeneratorContextManager
-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
-import queue
-import io
-import _thread
-import argparse
-import atexit
+from contextlib import _GeneratorContextManager
+from pathlib import Path
+from queue import Queue
+from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
 import base64
-import codecs
+import io
 import os
-import pathlib
-import ptpython.repl
-import pty
+import queue
 import re
 import shlex
 import shutil
@@ -22,8 +13,10 @@ import socket
 import subprocess
 import sys
 import tempfile
+import threading
 import time
-import unicodedata
+
+from test_driver.logger import rootlog
 
 CHAR_TO_KEY = {
     "A": "shift-a",
@@ -89,115 +82,10 @@ CHAR_TO_KEY = {
 }
 
 
-class Logger:
-    def __init__(self) -> None:
-        self.logfile = os.environ.get("LOGFILE", "/dev/null")
-        self.logfile_handle = codecs.open(self.logfile, "wb")
-        self.xml = XMLGenerator(self.logfile_handle, encoding="utf-8")
-        self.queue: "Queue[Dict[str, str]]" = Queue()
-
-        self.xml.startDocument()
-        self.xml.startElement("logfile", attrs={})
-
-        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()
-        self.logfile_handle.close()
-
-    def sanitise(self, message: str) -> str:
-        return "".join(ch for ch in message if unicodedata.category(ch)[0] != "C")
-
-    def maybe_prefix(self, message: str, attributes: Dict[str, str]) -> str:
-        if "machine" in attributes:
-            return "{}: {}".format(attributes["machine"], message)
-        return message
-
-    def log_line(self, message: str, attributes: Dict[str, str]) -> None:
-        self.xml.startElement("line", attributes)
-        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:
-        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:
-            self._eprint(
-                Style.DIM + "{} # {}".format(machine, message) + Style.RESET_ALL
-            )
-
-    def enqueue(self, item: Dict[str, str]) -> None:
-        self.queue.put(item)
-
-    def drain_log_queue(self) -> None:
-        try:
-            while True:
-                item = self.queue.get_nowait()
-                msg = self.sanitise(item["msg"])
-                del item["msg"]
-                self.log_line(msg, item)
-        except Empty:
-            pass
-
-    @contextmanager
-    def nested(self, message: str, attributes: Dict[str, str] = {}) -> Iterator[None]:
-        self._eprint(self.maybe_prefix(message, attributes))
-
-        self.xml.startElement("nest", attrs={})
-        self.xml.startElement("head", attributes)
-        self.xml.characters(message)
-        self.xml.endElement("head")
-
-        tic = time.time()
-        self.drain_log_queue()
-        yield
-        self.drain_log_queue()
-        toc = time.time()
-        self.log("({:.2f} seconds)".format(toc - tic))
-
-        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]:
@@ -229,6 +117,20 @@ def _perform_ocr_on_screenshot(
     return model_results
 
 
+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 StartCommand:
     """The Base Start Command knows how to append the necesary
     runtime qemu options as determined by a particular test driver
@@ -240,8 +142,8 @@ class StartCommand:
 
     def cmd(
         self,
-        monitor_socket_path: pathlib.Path,
-        shell_socket_path: pathlib.Path,
+        monitor_socket_path: Path,
+        shell_socket_path: Path,
         allow_reboot: bool = False,  # TODO: unused, legacy?
     ) -> str:
         display_opts = ""
@@ -273,8 +175,8 @@ class StartCommand:
 
     @staticmethod
     def build_environment(
-        state_dir: pathlib.Path,
-        shared_dir: pathlib.Path,
+        state_dir: Path,
+        shared_dir: Path,
     ) -> dict:
         # We make a copy to not update the current environment
         env = dict(os.environ)
@@ -289,10 +191,10 @@ class StartCommand:
 
     def run(
         self,
-        state_dir: pathlib.Path,
-        shared_dir: pathlib.Path,
-        monitor_socket_path: pathlib.Path,
-        shell_socket_path: pathlib.Path,
+        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),
@@ -335,7 +237,7 @@ class LegacyStartCommand(StartCommand):
         self,
         netBackendArgs: Optional[str] = None,
         netFrontendArgs: Optional[str] = None,
-        hda: Optional[Tuple[pathlib.Path, str]] = None,
+        hda: Optional[Tuple[Path, str]] = None,
         cdrom: Optional[str] = None,
         usb: Optional[str] = None,
         bios: Optional[str] = None,
@@ -395,23 +297,24 @@ class Machine:
     the machine lifecycle with the help of a start script / command."""
 
     name: str
-    tmp_dir: pathlib.Path
-    shared_dir: pathlib.Path
-    state_dir: pathlib.Path
-    monitor_path: pathlib.Path
-    shell_path: pathlib.Path
+    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] = None
-    pid: Optional[int] = None
-    monitor: Optional[socket.socket] = None
-    shell: Optional[socket.socket] = None
+    process: Optional[subprocess.Popen]
+    pid: Optional[int]
+    monitor: Optional[socket.socket]
+    shell: Optional[socket.socket]
+    serial_thread: Optional[threading.Thread]
 
-    booted: bool = False
-    connected: bool = False
+    booted: bool
+    connected: bool
     # Store last serial console lines for use
     # of wait_for_console_text
     last_lines: Queue = Queue()
@@ -421,7 +324,7 @@ class Machine:
 
     def __init__(
         self,
-        tmp_dir: pathlib.Path,
+        tmp_dir: Path,
         start_command: StartCommand,
         name: str = "machine",
         keep_vm_state: bool = False,
@@ -444,6 +347,15 @@ class Machine:
             self.cleanup_statedir()
         self.state_dir.mkdir(mode=0o700, exist_ok=True)
 
+        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(
@@ -454,7 +366,7 @@ class Machine:
         hda = None
         if args.get("hda"):
             hda_arg: str = args.get("hda", "")
-            hda_arg_path: pathlib.Path = pathlib.Path(hda_arg)
+            hda_arg_path: Path = Path(hda_arg)
             hda = (hda_arg_path, args.get("hdaInterface", ""))
         return LegacyStartCommand(
             netBackendArgs=args.get("netBackendArgs"),
@@ -481,23 +393,24 @@ class Machine:
         return rootlog.nested(msg, my_attrs)
 
     def wait_for_monitor_prompt(self) -> str:
-        assert self.monitor is not None
-        answer = ""
-        while True:
-            undecoded_answer = self.monitor.recv(1024)
-            if not undecoded_answer:
-                break
-            answer += undecoded_answer.decode()
-            if answer.endswith("(qemu) "):
-                break
-        return answer
+        with self.nested("waiting for monitor prompt"):
+            assert self.monitor is not None
+            answer = ""
+            while True:
+                undecoded_answer = self.monitor.recv(1024)
+                if not undecoded_answer:
+                    break
+                answer += undecoded_answer.decode()
+                if answer.endswith("(qemu) "):
+                    break
+            return answer
 
     def send_monitor_command(self, command: str) -> str:
-        message = ("{}\n".format(command)).encode()
-        self.log("sending monitor command: {}".format(command))
-        assert self.monitor is not None
-        self.monitor.send(message)
-        return self.wait_for_monitor_prompt()
+        with self.nested("sending monitor command: {}".format(command)):
+            message = ("{}\n".format(command)).encode()
+            assert self.monitor is not None
+            self.monitor.send(message)
+            return self.wait_for_monitor_prompt()
 
     def wait_for_unit(self, unit: str, user: Optional[str] = None) -> None:
         """Wait for a systemd unit to get into "active" state.
@@ -524,7 +437,12 @@ class Machine:
 
             return state == "active"
 
-        retry(check_active)
+        with self.nested(
+            "waiting for unit {}{}".format(
+                unit, f" with user {user}" if user is not None else ""
+            )
+        ):
+            retry(check_active)
 
     def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]:
         status, lines = self.systemctl('--no-pager show "{}"'.format(unit), user)
@@ -572,24 +490,45 @@ 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, timeout: Optional[int] = 900
+    ) -> Tuple[int, str]:
         self.connect()
 
-        out_command = "( set -euo pipefail; {} ); echo '|!=EOF' $?\n".format(command)
+        if timeout is not None:
+            command = "timeout {} sh -c {}".format(timeout, shlex.quote(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
@@ -604,12 +543,12 @@ class Machine:
             pass_fds=[self.shell.fileno()],
         )
 
-    def succeed(self, *commands: str) -> str:
+    def succeed(self, *commands: str, timeout: Optional[int] = None) -> str:
         """Execute each command and check that it succeeds."""
         output = ""
         for command in commands:
             with self.nested("must succeed: {}".format(command)):
-                (status, out) = self.execute(command)
+                (status, out) = self.execute(command, timeout=timeout)
                 if status != 0:
                     self.log("output: {}".format(out))
                     raise Exception(
@@ -618,12 +557,12 @@ class Machine:
                 output += out
         return output
 
-    def fail(self, *commands: str) -> str:
+    def fail(self, *commands: str, timeout: Optional[int] = None) -> str:
         """Execute each command and check that it fails."""
         output = ""
         for command in commands:
             with self.nested("must fail: {}".format(command)):
-                (status, out) = self.execute(command)
+                (status, out) = self.execute(command, timeout=timeout)
                 if status == 0:
                     raise Exception(
                         "command `{}` unexpectedly succeeded".format(command)
@@ -639,14 +578,14 @@ class Machine:
 
         def check_success(_: Any) -> bool:
             nonlocal output
-            status, output = self.execute(command)
+            status, output = self.execute(command, timeout=timeout)
             return status == 0
 
         with self.nested("waiting for success: {}".format(command)):
             retry(check_success, timeout)
             return output
 
-    def wait_until_fails(self, command: str) -> str:
+    def wait_until_fails(self, command: str, timeout: int = 900) -> str:
         """Wait until a command returns failure.
         Throws an exception on timeout.
         """
@@ -654,7 +593,7 @@ class Machine:
 
         def check_failure(_: Any) -> bool:
             nonlocal output
-            status, output = self.execute(command)
+            status, output = self.execute(command, timeout=timeout)
             return status != 0
 
         with self.nested("waiting for failure: {}".format(command)):
@@ -727,7 +666,8 @@ class Machine:
             status, _ = self.execute("nc -z localhost {}".format(port))
             return status != 0
 
-        retry(port_is_closed)
+        with self.nested("waiting for TCP port {} to be closed"):
+            retry(port_is_closed)
 
     def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
         return self.systemctl("start {}".format(jobname), user)
@@ -789,12 +729,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]))
@@ -811,11 +751,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
@@ -861,24 +801,25 @@ class Machine:
             retry(screen_matches)
 
     def wait_for_console_text(self, regex: str) -> None:
-        self.log("waiting for {} to appear on console".format(regex))
-        # Buffer the console output, this is needed
-        # to match multiline regexes.
-        console = io.StringIO()
-        while True:
-            try:
-                console.write(self.last_lines.get())
-            except queue.Empty:
-                self.sleep(1)
-                continue
-            console.seek(0)
-            matches = re.search(regex, console.read())
-            if matches is not None:
-                return
+        with self.nested("waiting for {} to appear on console".format(regex)):
+            # Buffer the console output, this is needed
+            # to match multiline regexes.
+            console = io.StringIO()
+            while True:
+                try:
+                    console.write(self.last_lines.get())
+                except queue.Empty:
+                    self.sleep(1)
+                    continue
+                console.seek(0)
+                matches = re.search(regex, console.read())
+                if matches is not None:
+                    return
 
     def send_key(self, key: str) -> None:
         key = CHAR_TO_KEY.get(key, key)
         self.send_monitor_command("sendkey {}".format(key))
+        time.sleep(0.01)
 
     def start(self) -> None:
         if self.booted:
@@ -886,12 +827,12 @@ class Machine:
 
         self.log("starting vm")
 
-        def clear(path: pathlib.Path) -> pathlib.Path:
+        def clear(path: Path) -> Path:
             if path.exists():
                 path.unlink()
             return path
 
-        def create_socket(path: pathlib.Path) -> socket.socket:
+        def create_socket(path: Path) -> socket.socket:
             s = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
             s.bind(str(path))
             s.listen(1)
@@ -921,7 +862,8 @@ class Machine:
                 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()
 
@@ -988,7 +930,7 @@ class Machine:
                 )
             return any(pattern.search(name) for name in names)
 
-        with self.nested("Waiting for a window to appear"):
+        with self.nested("waiting for a window to appear"):
             retry(window_is_visible)
 
     def sleep(self, secs: int) -> None:
@@ -1021,286 +963,9 @@ class Machine:
         assert self.process
         assert self.shell
         assert self.monitor
+        assert self.serial_thread
+
         self.process.terminate()
         self.shell.close()
         self.monitor.close()
-
-
-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: pathlib.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: pathlib.Path):
-        self.nr = nr
-        self.socket_dir = tmp_dir / f"vde{self.nr}.ctl"
-
-        # 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()
-
-        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 = pathlib.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)
-        ]
-
-        @atexit.register
-        def clean_up() -> None:
-            with rootlog.nested("clean up"):
-                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: self.machines[idx] for idx, m in enumerate(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 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 = pathlib.Path(os.environ.get("TMPDIR", tempfile.gettempdir()))
-        tmp_dir.mkdir(mode=0o700, exist_ok=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(self) -> None:
-        rootlog._print_serial_logs = False
-
-
-class EnvDefault(argparse.Action):
-    """An argpars Action that takes values from the specified
-    environment variable as the flags default value.
-    """
-
-    def __init__(self, envvar, required=False, default=None, nargs=None, **kwargs):  # type: ignore
-        if not default and envvar:
-            if envvar in os.environ:
-                if nargs is not None and (nargs.isdigit() or nargs in ["*", "+"]):
-                    default = os.environ[envvar].split()
-                else:
-                    default = os.environ[envvar]
-                kwargs["help"] = (
-                    kwargs["help"] + f" (default from environment: {default})"
-                )
-        if required and default:
-            required = False
-        super(EnvDefault, self).__init__(
-            default=default, required=required, nargs=nargs, **kwargs
-        )
-
-    def __call__(self, parser, namespace, values, option_string=None):  # type: ignore
-        setattr(namespace, self.dest, values)
-
-
-if __name__ == "__main__":
-    arg_parser = argparse.ArgumentParser(prog="nixos-test-driver")
-    arg_parser.add_argument(
-        "-K",
-        "--keep-vm-state",
-        help="re-use a VM state coming from a previous run",
-        action="store_true",
-    )
-    arg_parser.add_argument(
-        "-I",
-        "--interactive",
-        help="drop into a python repl and run the tests interactively",
-        action="store_true",
-    )
-    arg_parser.add_argument(
-        "--start-scripts",
-        metavar="START-SCRIPT",
-        action=EnvDefault,
-        envvar="startScripts",
-        nargs="*",
-        help="start scripts for participating virtual machines",
-    )
-    arg_parser.add_argument(
-        "--vlans",
-        metavar="VLAN",
-        action=EnvDefault,
-        envvar="vlans",
-        nargs="*",
-        help="vlans to span by the driver",
-    )
-    arg_parser.add_argument(
-        "testscript",
-        action=EnvDefault,
-        envvar="testScript",
-        help="the test script to run",
-        type=pathlib.Path,
-    )
-
-    args = arg_parser.parse_args()
-
-    if not args.keep_vm_state:
-        rootlog.info("Machine state will be reset. To keep it, pass --keep-vm-state")
-
-    driver = Driver(
-        args.start_scripts, args.vlans, args.testscript.read_text(), args.keep_vm_state
-    )
-
-    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")
+        self.serial_thread.join()
diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/vlan.py b/nixpkgs/nixos/lib/test-driver/test_driver/vlan.py
new file mode 100644
index 000000000000..e5c8f07b4edf
--- /dev/null
+++ b/nixpkgs/nixos/lib/test-driver/test_driver/vlan.py
@@ -0,0 +1,58 @@
+from pathlib import Path
+import io
+import os
+import pty
+import subprocess
+
+from test_driver.logger import rootlog
+
+
+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"
+
+        # 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()
+
+        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()
diff --git a/nixpkgs/nixos/lib/testing-python.nix b/nixpkgs/nixos/lib/testing-python.nix
index dbba9e4c4452..365e22714573 100644
--- a/nixpkgs/nixos/lib/testing-python.nix
+++ b/nixpkgs/nixos/lib/testing-python.nix
@@ -16,65 +16,6 @@ rec {
 
   inherit pkgs;
 
-  # Reifies and correctly wraps the python test driver for
-  # the respective qemu version and with or without ocr support
-  pythonTestDriver = {
-      qemu_pkg ? pkgs.qemu_test
-    , enableOCR ? false
-  }:
-    let
-      name = "nixos-test-driver";
-      testDriverScript = ./test-driver/test-driver.py;
-      ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; };
-      imagemagick_tiff = imagemagick_light.override { inherit libtiff; };
-    in stdenv.mkDerivation {
-      inherit name;
-
-      nativeBuildInputs = [ makeWrapper ];
-      buildInputs = [ (python3.withPackages (p: [ p.ptpython p.colorama ])) ];
-      checkInputs = with python3Packages; [ pylint black mypy ];
-
-      dontUnpack = true;
-
-      preferLocalBuild = true;
-
-      buildPhase = ''
-        python <<EOF
-        from pydoc import importfile
-        with open('driver-symbols', 'w') as fp:
-          t = importfile('${testDriverScript}')
-          d = t.Driver([],[],"")
-          test_symbols = d.test_symbols()
-          fp.write(','.join(test_symbols.keys()))
-        EOF
-      '';
-
-      doCheck = true;
-      checkPhase = ''
-        mypy --disallow-untyped-defs \
-             --no-implicit-optional \
-             --ignore-missing-imports ${testDriverScript}
-        pylint --errors-only ${testDriverScript}
-        black --check --diff ${testDriverScript}
-      '';
-
-      installPhase =
-        ''
-          mkdir -p $out/bin
-          cp ${testDriverScript} $out/bin/nixos-test-driver
-          chmod u+x $out/bin/nixos-test-driver
-          # TODO: copy user script part into this file (append)
-
-          wrapProgram $out/bin/nixos-test-driver \
-            --argv0 ${name} \
-            --prefix PATH : "${lib.makeBinPath [ qemu_pkg vde2 netpbm coreutils socat ]}" \
-            ${lib.optionalString enableOCR
-              "--prefix PATH : '${ocrProg}/bin:${imagemagick_tiff}/bin'"} \
-
-          install -m 0644 -vD driver-symbols $out/nix-support/driver-symbols
-        '';
-    };
-
   # Run an automated test suite in the given virtual network.
   runTests = { driver, pos }:
     stdenv.mkDerivation {
@@ -112,8 +53,15 @@ rec {
     , passthru ? {}
   }:
     let
-      # FIXME: get this pkg from the module system
-      testDriver = pythonTestDriver { inherit qemu_pkg enableOCR;};
+      # Reifies and correctly wraps the python test driver for
+      # the respective qemu version and with or without ocr support
+      testDriver = pkgs.callPackage ./test-driver {
+        inherit enableOCR;
+        qemu_pkg = qemu_test;
+        imagemagick_light = imagemagick_light.override { inherit libtiff; };
+        tesseract4 = tesseract4.override { enableLanguages = [ "eng" ]; };
+      };
+
 
       testDriverName =
         let
@@ -134,7 +82,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
@@ -176,10 +126,11 @@ rec {
         echo -n "$testScript" > $out/test-script
         ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
 
+        ${testDriver}/bin/generate-driver-symbols
         ${lib.optionalString (!skipLint) ''
           PYFLAKES_BUILTINS="$(
             echo -n ${lib.escapeShellArg (lib.concatStringsSep "," nodeHostNames)},
-            < ${lib.escapeShellArg "${testDriver}/nix-support/driver-symbols"}
+            < ${lib.escapeShellArg "driver-symbols"}
           )" ${python3Packages.pyflakes}/bin/pyflakes $out/test-script
         ''}
 
@@ -209,11 +160,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 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;
diff --git a/nixpkgs/nixos/lib/utils.nix b/nixpkgs/nixos/lib/utils.nix
index 439b627dc385..bbebf8ba35a0 100644
--- a/nixpkgs/nixos/lib/utils.nix
+++ b/nixpkgs/nixos/lib/utils.nix
@@ -1,4 +1,4 @@
-pkgs: with pkgs.lib;
+{ lib, config, pkgs }: with lib;
 
 rec {
 
@@ -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" "/var/lib/nixos" "/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
@@ -165,4 +165,9 @@ rec {
       ${builtins.toJSON set}
       EOF
     '';
+
+  systemdUtils = {
+    lib = import ./systemd-lib.nix { inherit lib config pkgs; };
+    unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; };
+  };
 }
diff --git a/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix
index fcb369e87ff9..6358ec68f7cf 100644
--- a/nixpkgs/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixpkgs/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 {
 
@@ -106,10 +107,12 @@ in {
          --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
          --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/nixpkgs/nixos/maintainers/scripts/ec2/create-amis.sh b/nixpkgs/nixos/maintainers/scripts/ec2/create-amis.sh
index 712d9b548ff0..797fe03e2095 100755
--- a/nixpkgs/nixos/maintainers/scripts/ec2/create-amis.sh
+++ b/nixpkgs/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
@@ -65,13 +68,18 @@ read_image_info() {
 # We handle a single image per invocation, store all attributes in
 # globals for convenience.
 zfs_disks=$(read_image_info .disks)
-image_label="$(read_image_info .label)${zfs_disks:+-ZFS}"
+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_files=( $(read_image_info "${zfs_disks:+.disks.root}.file") )
+image_files=( $(read_image_info ".disks.root.file") )
 
-image_logical_bytes=$(read_image_info "${zfs_disks:+.disks.boot}.logical_bytes")
+image_logical_bytes=$(read_image_info "${zfs_boot:-.disks.root}.logical_bytes")
 
-if [[ -n "$zfs_disks" ]]; then
+if [[ -n "$is_zfs_image" ]]; then
   image_files+=( $(read_image_info .disks.boot.file) )
 fi
 
@@ -192,7 +200,7 @@ upload_image() {
     for image_file in "${image_files[@]}"; do
         local aws_path=${image_file#/}
 
-        if [[ -n "$zfs_disks" ]]; then
+        if [[ -n "$is_zfs_image" ]]; then
             local suffix=${image_file%.*}
             suffix=${suffix##*.}
         fi
@@ -239,7 +247,7 @@ upload_image() {
             "DeviceName=/dev/xvda,Ebs={SnapshotId=$snapshot_id,VolumeSize=$image_logical_gigabytes,DeleteOnTermination=true,VolumeType=gp3}"
         )
 
-        if [[ -n "$zfs_disks" ]]; then
+        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")
@@ -270,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'
               )
diff --git a/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix b/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
new file mode 100644
index 000000000000..74634fd1671c
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image.nix b/nixpkgs/nixos/maintainers/scripts/lxd/lxd-image.nix
new file mode 100644
index 000000000000..c76b9fcc7f77
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/maintainers/scripts/lxd/nix.tpl b/nixpkgs/nixos/maintainers/scripts/lxd/nix.tpl
new file mode 100644
index 000000000000..307258ddc628
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/config/console.nix b/nixpkgs/nixos/modules/config/console.nix
index 98f942ee63f5..168bebd8d06a 100644
--- a/nixpkgs/nixos/modules/config/console.nix
+++ b/nixpkgs/nixos/modules/config/console.nix
@@ -116,7 +116,7 @@ in
     { console.keyMap = with config.services.xserver;
         mkIf cfg.useXkbConfig
           (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
-            '${pkgs.ckbcomp}/bin/ckbcomp' \
+            '${pkgs.buildPackages.ckbcomp}/bin/ckbcomp' \
               ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
                 "-I${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
               } \
diff --git a/nixpkgs/nixos/modules/config/fonts/fontdir.nix b/nixpkgs/nixos/modules/config/fonts/fontdir.nix
index db4b6c638ab4..560918302ca6 100644
--- a/nixpkgs/nixos/modules/config/fonts/fontdir.nix
+++ b/nixpkgs/nixos/modules/config/fonts/fontdir.nix
@@ -39,6 +39,7 @@ in
       decompressFonts = mkOption {
         type = types.bool;
         default = config.programs.xwayland.enable;
+        defaultText = literalExpression "config.programs.xwayland.enable";
         description = ''
           Whether to decompress fonts in
           <filename>/run/current-system/sw/share/X11/fonts</filename>.
diff --git a/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
index 7441f4de40eb..ff9aa7c6a047 100644
--- a/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
+++ b/nixpkgs/nixos/modules/config/gtk/gtk-icon-cache.nix
@@ -6,6 +6,7 @@ with lib;
     gtk.iconCache.enable = mkOption {
       type = types.bool;
       default = config.services.xserver.enable;
+      defaultText = literalExpression "config.services.xserver.enable";
       description = ''
         Whether to build icon theme caches for GTK applications.
       '';
diff --git a/nixpkgs/nixos/modules/config/i18n.nix b/nixpkgs/nixos/modules/config/i18n.nix
index 545d4a3dca61..5b8d5b214496 100644
--- a/nixpkgs/nixos/modules/config/i18n.nix
+++ b/nixpkgs/nixos/modules/config/i18n.nix
@@ -14,6 +14,12 @@ with lib;
           allLocales = any (x: x == "all") config.i18n.supportedLocales;
           locales = config.i18n.supportedLocales;
         };
+        defaultText = literalExpression ''
+          pkgs.buildPackages.glibcLocales.override {
+            allLocales = any (x: x == "all") config.i18n.supportedLocales;
+            locales = config.i18n.supportedLocales;
+          }
+        '';
         example = literalExpression "pkgs.glibcLocales";
         description = ''
           Customized pkg.glibcLocales package.
diff --git a/nixpkgs/nixos/modules/config/networking.nix b/nixpkgs/nixos/modules/config/networking.nix
index 11307e331200..133a150df82c 100644
--- a/nixpkgs/nixos/modules/config/networking.nix
+++ b/nixpkgs/nixos/modules/config/networking.nix
@@ -1,12 +1,13 @@
 # /etc files related to networking, such as /etc/services.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
 
   cfg = config.networking;
+  opt = options.networking;
 
   localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]));
 
@@ -78,6 +79,7 @@ in
       httpProxy = lib.mkOption {
         type = types.nullOr types.str;
         default = cfg.proxy.default;
+        defaultText = literalExpression "config.${opt.proxy.default}";
         description = ''
           This option specifies the http_proxy environment variable.
         '';
@@ -87,6 +89,7 @@ in
       httpsProxy = lib.mkOption {
         type = types.nullOr types.str;
         default = cfg.proxy.default;
+        defaultText = literalExpression "config.${opt.proxy.default}";
         description = ''
           This option specifies the https_proxy environment variable.
         '';
@@ -96,6 +99,7 @@ in
       ftpProxy = lib.mkOption {
         type = types.nullOr types.str;
         default = cfg.proxy.default;
+        defaultText = literalExpression "config.${opt.proxy.default}";
         description = ''
           This option specifies the ftp_proxy environment variable.
         '';
@@ -105,6 +109,7 @@ in
       rsyncProxy = lib.mkOption {
         type = types.nullOr types.str;
         default = cfg.proxy.default;
+        defaultText = literalExpression "config.${opt.proxy.default}";
         description = ''
           This option specifies the rsync_proxy environment variable.
         '';
@@ -114,6 +119,7 @@ in
       allProxy = lib.mkOption {
         type = types.nullOr types.str;
         default = cfg.proxy.default;
+        defaultText = literalExpression "config.${opt.proxy.default}";
         description = ''
           This option specifies the all_proxy environment variable.
         '';
diff --git a/nixpkgs/nixos/modules/config/swap.nix b/nixpkgs/nixos/modules/config/swap.nix
index ff2ae1da31bd..2b94b954cb80 100644
--- a/nixpkgs/nixos/modules/config/swap.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/config/system-path.nix b/nixpkgs/nixos/modules/config/system-path.nix
index 6ff4ec2921cf..875c4c9c4415 100644
--- a/nixpkgs/nixos/modules/config/system-path.nix
+++ b/nixpkgs/nixos/modules/config/system-path.nix
@@ -41,12 +41,17 @@ let
       pkgs.zstd
     ];
 
-    defaultPackages = map (pkg: setPrio ((pkg.meta.priority or 5) + 3) pkg)
-      [ pkgs.nano
-        pkgs.perl
-        pkgs.rsync
-        pkgs.strace
-      ];
+  defaultPackageNames =
+    [ "nano"
+      "perl"
+      "rsync"
+      "strace"
+    ];
+  defaultPackages =
+    map
+      (n: let pkg = pkgs.${n}; in setPrio ((pkg.meta.priority or 5) + 3) pkg)
+      defaultPackageNames;
+  defaultPackagesText = "[ ${concatMapStringsSep " " (n: "pkgs.${n}") defaultPackageNames } ]";
 
 in
 
@@ -73,6 +78,11 @@ in
       defaultPackages = mkOption {
         type = types.listOf types.package;
         default = defaultPackages;
+        defaultText = literalDocBook ''
+          these packages, with their <literal>meta.priority</literal> numerically increased
+          (thus lowering their installation priority):
+          <programlisting>${defaultPackagesText}</programlisting>
+        '';
         example = [];
         description = ''
           Set of default packages that aren't strictly necessary
diff --git a/nixpkgs/nixos/modules/config/users-groups.nix b/nixpkgs/nixos/modules/config/users-groups.nix
index 629905e60955..a34d28143418 100644
--- a/nixpkgs/nixos/modules/config/users-groups.nix
+++ b/nixpkgs/nixos/modules/config/users-groups.nix
@@ -558,6 +558,7 @@ in {
       input.gid = ids.gids.input;
       kvm.gid = ids.gids.kvm;
       render.gid = ids.gids.render;
+      sgx.gid = ids.gids.sgx;
       shadow.gid = ids.gids.shadow;
     };
 
diff --git a/nixpkgs/nixos/modules/hardware/all-firmware.nix b/nixpkgs/nixos/modules/hardware/all-firmware.nix
index bdf90816740c..ce87f9e8be8a 100644
--- a/nixpkgs/nixos/modules/hardware/all-firmware.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix b/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix
new file mode 100644
index 000000000000..046479400587
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/cpu/intel-sgx.nix
@@ -0,0 +1,47 @@
+{ config, lib, ... }:
+with lib;
+let
+  cfg = config.hardware.cpu.intel.sgx.provision;
+  defaultGroup = "sgx_prv";
+in
+{
+  options.hardware.cpu.intel.sgx.provision = {
+    enable = mkEnableOption "access to the Intel SGX provisioning device";
+    user = mkOption {
+      description = "Owner to assign to the SGX provisioning device.";
+      type = types.str;
+      default = "root";
+    };
+    group = mkOption {
+      description = "Group to assign to the SGX provisioning device.";
+      type = types.str;
+      default = defaultGroup;
+    };
+    mode = mkOption {
+      description = "Mode to set for the SGX provisioning device.";
+      type = types.str;
+      default = "0660";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = hasAttr cfg.user config.users.users;
+        message = "Given user does not exist";
+      }
+      {
+        assertion = (cfg.group == defaultGroup) || (hasAttr cfg.group config.users.groups);
+        message = "Given group does not exist";
+      }
+    ];
+
+    users.groups = optionalAttrs (cfg.group == defaultGroup) {
+      "${cfg.group}" = { };
+    };
+
+    services.udev.extraRules = ''
+      SUBSYSTEM=="misc", KERNEL=="sgx_provision", OWNER="${cfg.user}", GROUP="${cfg.group}", MODE="${cfg.mode}"
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/hardware/flirc.nix b/nixpkgs/nixos/modules/hardware/flirc.nix
new file mode 100644
index 000000000000..94ec715b9fa5
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/hardware/gkraken.nix b/nixpkgs/nixos/modules/hardware/gkraken.nix
new file mode 100644
index 000000000000..97d15369db0a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix b/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix
new file mode 100644
index 000000000000..6e5fcda6b851
--- /dev/null
+++ b/nixpkgs/nixos/modules/hardware/gpgsmartcards.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  # gnupg's manual describes how to setup ccid udev rules:
+  #   https://www.gnupg.org/howtos/card-howto/en/ch02s03.html
+  # gnupg folks advised me (https://dev.gnupg.org/T5409) to look at debian's rules:
+  # https://salsa.debian.org/debian/gnupg2/-/blob/debian/main/debian/scdaemon.udev
+
+  # the latest rev of the entire debian gnupg2 repo as of 2021-04-28
+  # the scdaemon.udev file was last commited on 2021-01-05 (7817a03):
+  scdaemonUdevRev = "01898735a015541e3ffb43c7245ac1e612f40836";
+
+  scdaemonRules = pkgs.fetchurl {
+    url = "https://salsa.debian.org/debian/gnupg2/-/raw/${scdaemonUdevRev}/debian/scdaemon.udev";
+    sha256 = "08v0vp6950bz7galvc92zdss89y9vcwbinmbfcdldy8x72w6rqr3";
+  };
+
+  # per debian's udev deb hook (https://man7.org/linux/man-pages/man1/dh_installudev.1.html)
+  destination = "60-scdaemon.rules";
+
+  scdaemonUdevRulesPkg = pkgs.runCommandNoCC "scdaemon-udev-rules" {} ''
+    loc="$out/lib/udev/rules.d/"
+    mkdir -p "''${loc}"
+    cp "${scdaemonRules}" "''${loc}/${destination}"
+  '';
+
+  cfg = config.hardware.gpgSmartcards;
+in {
+  options.hardware.gpgSmartcards = {
+    enable = mkEnableOption "udev rules for gnupg smart cards";
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ scdaemonUdevRulesPkg ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix b/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix
index 5cb09e5af499..bb69cfa0bf09 100644
--- a/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixpkgs/nixos/modules/hardware/keyboard/zsa.nix
@@ -5,7 +5,6 @@ let
   cfg = config.hardware.keyboard.zsa;
 in
 {
-  # TODO: make group configurable like in https://github.com/NixOS/nixpkgs/blob/0b2b4b8c4e729535a61db56468809c5c2d3d175c/pkgs/tools/security/nitrokey-app/udev-rules.nix ?
   options.hardware.keyboard.zsa = {
     enable = mkOption {
       type = types.bool;
@@ -14,7 +13,6 @@ in
         Enables udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
         You need it when you want to flash a new configuration on the keyboard
         or use their live training in the browser.
-        Access to the keyboard is granted to users in the "plugdev" group.
         You may want to install the wally-cli package.
       '';
     };
@@ -22,6 +20,5 @@ in
 
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.zsa-udev-rules ];
-    users.groups.plugdev = {};
   };
 }
diff --git a/nixpkgs/nixos/modules/hardware/pcmcia.nix b/nixpkgs/nixos/modules/hardware/pcmcia.nix
index d7d002ae6c8a..aef35a28e54d 100644
--- a/nixpkgs/nixos/modules/hardware/pcmcia.nix
+++ b/nixpkgs/nixos/modules/hardware/pcmcia.nix
@@ -35,6 +35,7 @@ in
 
       config = mkOption {
         default = null;
+        type = types.nullOr types.path;
         description = ''
           Path to the configuration file which maps the memory, IRQs
           and ports used by the PCMCIA hardware.
diff --git a/nixpkgs/nixos/modules/hardware/printers.nix b/nixpkgs/nixos/modules/hardware/printers.nix
index 7bab4f7038d9..ef07542950ba 100644
--- a/nixpkgs/nixos/modules/hardware/printers.nix
+++ b/nixpkgs/nixos/modules/hardware/printers.nix
@@ -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/nixpkgs/nixos/modules/hardware/system-76.nix b/nixpkgs/nixos/modules/hardware/system-76.nix
index d4896541dbae..ca40ee0ebb37 100644
--- a/nixpkgs/nixos/modules/hardware/system-76.nix
+++ b/nixpkgs/nixos/modules/hardware/system-76.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
-  inherit (lib) mkOption mkEnableOption types mkIf mkMerge optional versionOlder;
+  inherit (lib) literalExpression mkOption mkEnableOption types mkIf mkMerge optional versionOlder;
   cfg = config.hardware.system76;
+  opt = options.hardware.system76;
 
   kpkgs = config.boot.kernelPackages;
   modules = [ "system76" "system76-io" ] ++ (optional (versionOlder kpkgs.kernel.version "5.5") "system76-acpi");
@@ -60,6 +61,7 @@ in {
 
       firmware-daemon.enable = mkOption {
         default = cfg.enableAll;
+        defaultText = literalExpression "config.${opt.enableAll}";
         example = true;
         description = "Whether to enable the system76 firmware daemon";
         type = types.bool;
@@ -67,6 +69,7 @@ in {
 
       kernel-modules.enable = mkOption {
         default = cfg.enableAll;
+        defaultText = literalExpression "config.${opt.enableAll}";
         example = true;
         description = "Whether to make the system76 out-of-tree kernel modules available";
         type = types.bool;
@@ -74,6 +77,7 @@ in {
 
       power-daemon.enable = mkOption {
         default = cfg.enableAll;
+        defaultText = literalExpression "config.${opt.enableAll}";
         example = true;
         description = "Whether to enable the system76 power daemon";
         type = types.bool;
diff --git a/nixpkgs/nixos/modules/hardware/video/hidpi.nix b/nixpkgs/nixos/modules/hardware/video/hidpi.nix
index c480cc481dfc..ac72b652504e 100644
--- a/nixpkgs/nixos/modules/hardware/video/hidpi.nix
+++ b/nixpkgs/nixos/modules/hardware/video/hidpi.nix
@@ -12,6 +12,5 @@ with lib;
     boot.loader.systemd-boot.consoleMode = mkDefault "1";
 
     # TODO Find reasonable defaults X11 & wayland
-    services.xserver.dpi = lib.mkDefault 192;
   };
 }
diff --git a/nixpkgs/nixos/modules/hardware/video/nvidia.nix b/nixpkgs/nixos/modules/hardware/video/nvidia.nix
index 8f6b5c22ea4f..ff4225dc29ad 100644
--- a/nixpkgs/nixos/modules/hardware/video/nvidia.nix
+++ b/nixpkgs/nixos/modules/hardware/video/nvidia.nix
@@ -179,7 +179,7 @@ in
   in mkIf enabled {
     assertions = [
       {
-        assertion = with config.services.xserver.displayManager; gdm.nvidiaWayland -> cfg.modesetting.enable;
+        assertion = with config.services.xserver.displayManager; (gdm.enable && gdm.nvidiaWayland) -> cfg.modesetting.enable;
         message = "You cannot use wayland with GDM without modesetting enabled for NVIDIA drivers, set `hardware.nvidia.modesetting.enable = true`";
       }
 
@@ -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/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix b/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
index 4812cacabaf3..30610b4f4260 100644
--- a/nixpkgs/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixpkgs/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
 
diff --git a/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix
index cb509b7340bd..065cea470fbb 100644
--- a/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/nzp4m3cmm7wawk031byh8jg4cdzjq212-nix-2.3.16";
-  i686-linux = "/nix/store/zsaza9pwim617ak15fsc31lv65b9w3in-nix-2.3.16";
-  aarch64-linux = "/nix/store/7f6z40gyd405yd50qkyzwilnqw106bx8-nix-2.3.16";
-  x86_64-darwin = "/nix/store/c43kyri67ia8mibs0id5ara7gqwlkybf-nix-2.3.16";
-  aarch64-darwin = "/nix/store/6jwhak3cvsgnbqs540n27g8pxnk427fr-nix-2.3.16";
+  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/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
index 2a6c3ab11497..490ede04e6bb 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-build-vms/nixos-build-vms.sh
@@ -1,4 +1,5 @@
 #! @runtimeShell@ -e
+# shellcheck shell=bash
 
 # Shows the usage of this command to the user
 
@@ -29,12 +30,12 @@ while [ $# -gt 0 ]; do
         nixBuildArgs+=("--option" "$1" "$2"); shift
         ;;
       *)
-        if [ ! -z "$networkExpr" ]; then
+        if [ -n "$networkExpr" ]; then
           echo "Network expression already set!"
           showUsage
           exit 1
         fi
-        networkExpr="$(readlink -f $1)"
+        networkExpr="$(readlink -f "$1")"
         ;;
     esac
 
@@ -49,4 +50,4 @@ fi
 
 # Build a network of VMs
 nix-build '<nixpkgs/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix>' \
-    --argstr networkExpr $networkExpr "${nixBuildArgs[@]}"
+    --argstr networkExpr "$networkExpr" "${nixBuildArgs[@]}"
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh b/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
index 450d77618148..6469d9faa038 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-enter.sh
@@ -1,4 +1,5 @@
 #! @runtimeShell@
+# shellcheck shell=bash
 
 set -e
 
@@ -60,6 +61,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 +98,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/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl b/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
index 7bc55e67134b..fe8c4fb1a6b5 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixpkgs/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/nixpkgs/nixos/modules/installer/tools/nixos-install.sh b/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
index ea9667995e13..fc4a69aa17d3 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-install.sh
@@ -1,4 +1,5 @@
 #! @runtimeShell@
+# shellcheck shell=bash
 
 set -e
 shopt -s nullglob
@@ -58,7 +59,7 @@ while [ "$#" -gt 0 ]; do
         --no-channel-copy)
             noChannelCopy=1
             ;;
-        --no-root-passwd)
+        --no-root-password|--no-root-passwd)
             noRootPasswd=1
             ;;
         --no-bootloader)
diff --git a/nixpkgs/nixos/modules/installer/tools/nixos-version.sh b/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
index f5e3f32b3c63..59a9c572b418 100644
--- a/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
+++ b/nixpkgs/nixos/modules/installer/tools/nixos-version.sh
@@ -1,4 +1,5 @@
 #! @runtimeShell@
+# shellcheck shell=bash
 
 case "$1" in
   -h|--help)
diff --git a/nixpkgs/nixos/modules/misc/documentation.nix b/nixpkgs/nixos/modules/misc/documentation.nix
index e32e49dcd88e..1c65c09329e6 100644
--- a/nixpkgs/nixos/modules/misc/documentation.nix
+++ b/nixpkgs/nixos/modules/misc/documentation.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, baseModules, extraModules, modules, modulesPath, ... }:
+{ config, lib, pkgs, extendModules, noUserModules, ... }:
 
 with lib;
 
@@ -6,11 +6,8 @@ let
 
   cfg = config.documentation;
 
-  manualModules =
-    baseModules
-    # Modules for which to show options even when not imported
-    ++ [ ../virtualisation/qemu-vm.nix ]
-    ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
+  /* Modules for which to show options even when not imported. */
+  extraDocModules = [ ../virtualisation/qemu-vm.nix ];
 
   /* For the purpose of generating docs, evaluate options with each derivation
     in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
@@ -24,13 +21,10 @@ let
     extraSources = cfg.nixos.extraModuleSources;
     options =
       let
-        scrubbedEval = evalModules {
-          modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ manualModules;
-          args = (config._module.args) // { modules = [ ]; };
-          specialArgs = {
-            pkgs = scrubDerivations "pkgs" pkgs;
-            inherit modulesPath;
-          };
+        extendNixOS = if cfg.nixos.includeAllModules then extendModules else noUserModules.extendModules;
+        scrubbedEval = extendNixOS {
+          modules = extraDocModules;
+          specialArgs.pkgs = scrubDerivations "pkgs" pkgs;
         };
         scrubDerivations = namePrefix: pkgSet: mapAttrs
           (name: value:
@@ -109,8 +103,8 @@ in
         type = types.bool;
         default = true;
         description = ''
-          Whether to install manual pages and the <command>man</command> command.
-          This also includes "man" outputs.
+          Whether to install manual pages.
+          This also includes <literal>man</literal> outputs.
         '';
       };
 
@@ -127,27 +121,18 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Whether to generate the manual page index caches using
-          <literal>mandb(8)</literal>. This allows searching for a page or
-          keyword using utilities like <literal>apropos(1)</literal>.
-        '';
-      };
-
-      man.manualPages = mkOption {
-        type = types.path;
-        default = pkgs.buildEnv {
-          name = "man-paths";
-          paths = config.environment.systemPackages;
-          pathsToLink = [ "/share/man" ];
-          extraOutputsToInstall = ["man"];
-          ignoreCollisions = true;
-        };
-        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
-          <literal>/share/man</literal>; see the source for an example.
-          Advanced users can make this a content-addressed derivation to save a few rebuilds.
+          Whether to generate the manual page index caches.
+          This allows searching for a page or
+          keyword using utilities like
+          <citerefentry>
+            <refentrytitle>apropos</refentrytitle>
+            <manvolnum>1</manvolnum>
+          </citerefentry>
+          and the <literal>-k</literal> option of
+          <citerefentry>
+            <refentrytitle>man</refentrytitle>
+            <manvolnum>1</manvolnum>
+          </citerefentry>.
         '';
       };
 
@@ -231,32 +216,22 @@ in
   };
 
   config = mkIf cfg.enable (mkMerge [
+    {
+      assertions = [
+        {
+          assertion = !(cfg.man.man-db.enable && cfg.man.mandoc.enable);
+          message = ''
+            man-db and mandoc can't be used as the default man page viewer at the same time!
+          '';
+        }
+      ];
+    }
 
+    # The actual implementation for this lives in man-db.nix or mandoc.nix,
+    # depending on which backend is active.
     (mkIf cfg.man.enable {
-      environment.systemPackages = [ pkgs.man-db ];
       environment.pathsToLink = [ "/share/man" ];
       environment.extraOutputsToInstall = [ "man" ] ++ optional cfg.dev.enable "devman";
-      environment.etc."man_db.conf".text =
-        let
-          manualCache = pkgs.runCommandLocal "man-cache" { } ''
-            echo "MANDB_MAP ${cfg.man.manualPages}/share/man $out" > man.conf
-            ${pkgs.man-db}/bin/mandb -C man.conf -psc >/dev/null 2>&1
-          '';
-        in
-        ''
-          # Manual pages paths for NixOS
-          MANPATH_MAP /run/current-system/sw/bin /run/current-system/sw/share/man
-          MANPATH_MAP /run/wrappers/bin          /run/current-system/sw/share/man
-
-          ${optionalString cfg.man.generateCaches ''
-          # Generated manual pages cache for NixOS (immutable)
-          MANDB_MAP /run/current-system/sw/share/man ${manualCache}
-          ''}
-          # Manual pages caches for NixOS
-          MANDB_MAP /run/current-system/sw/share/man /var/cache/man/nixos
-
-          ${cfg.man.extraConfig}
-        '';
     })
 
     (mkIf cfg.info.enable {
diff --git a/nixpkgs/nixos/modules/misc/extra-arguments.nix b/nixpkgs/nixos/modules/misc/extra-arguments.nix
index 8716e3d9fef2..48891b440498 100644
--- a/nixpkgs/nixos/modules/misc/extra-arguments.nix
+++ b/nixpkgs/nixos/modules/misc/extra-arguments.nix
@@ -1,7 +1,7 @@
-{ pkgs, ... }:
+{ lib, config, pkgs, ... }:
 
 {
   _module.args = {
-    utils = import ../../lib/utils.nix pkgs;
+    utils = import ../../lib/utils.nix { inherit lib config pkgs; };
   };
 }
diff --git a/nixpkgs/nixos/modules/misc/ids.nix b/nixpkgs/nixos/modules/misc/ids.nix
index 08da55df2dc2..69feaad1965c 100644
--- a/nixpkgs/nixos/modules/misc/ids.nix
+++ b/nixpkgs/nixos/modules/misc/ids.nix
@@ -296,7 +296,7 @@ in
       infinoted = 264;
       sickbeard = 265;
       headphones = 266;
-      couchpotato = 267;
+      # couchpotato = 267; # unused, removed 2022-01-01
       gogs = 268;
       #pdns-recursor = 269; # dynamically allocated as of 2020-20-18
       #kresd = 270; # switched to "knot-resolver" with dynamic ID
@@ -351,6 +351,7 @@ in
       hqplayer = 319;
       moonraker = 320;
       distcc = 321;
+      webdav = 322;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -602,7 +603,7 @@ in
       infinoted = 264;
       sickbeard = 265;
       headphones = 266;
-      couchpotato = 267;
+      # couchpotato = 267; # unused, removed 2022-01-01
       gogs = 268;
       #kresd = 270; # switched to "knot-resolver" with dynamic ID
       #rpc = 271; # unused
@@ -638,7 +639,7 @@ in
       qemu-libvirtd = 301;
       kvm = 302; # default udev rules from systemd requires these
       render = 303; # default udev rules from systemd requires these
-      # zeronet = 304; # removed 2019-01-03
+      sgx = 304; # default udev rules from systemd requires these
       lirc = 305;
       lidarr = 306;
       slurm = 307;
@@ -656,6 +657,7 @@ in
       hqplayer = 319;
       moonraker = 320;
       distcc = 321;
+      webdav = 322;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixpkgs/nixos/modules/misc/locate.nix b/nixpkgs/nixos/modules/misc/locate.nix
index 2f2986c2fec5..5fd82aa963bf 100644
--- a/nixpkgs/nixos/modules/misc/locate.nix
+++ b/nixpkgs/nixos/modules/misc/locate.nix
@@ -84,12 +84,15 @@ in {
         "bdev"
         "binfmt"
         "binfmt_misc"
+        "ceph"
         "cgroup"
+        "cgroup2"
         "cifs"
         "coda"
         "configfs"
         "cramfs"
         "cpuset"
+        "curlftpfs"
         "debugfs"
         "devfs"
         "devpts"
@@ -101,6 +104,13 @@ in {
         "ftpfs"
         "fuse"
         "fusectl"
+        "fusesmb"
+        "fuse.ceph"
+        "fuse.glusterfs"
+        "fuse.gvfsd-fuse"
+        "fuse.mfs"
+        "fuse.rclone"
+        "fuse.rozofs"
         "fuse.sshfs"
         "gfs"
         "gfs2"
@@ -110,9 +120,15 @@ in {
         "iso9660"
         "jffs2"
         "lustre"
+        "lustre_lite"
         "misc"
+        "mfs"
         "mqueue"
         "ncpfs"
+        "nfs"
+        "NFS"
+        "nfs4"
+        "nfsd"
         "nnpfs"
         "ocfs"
         "ocfs2"
@@ -127,16 +143,14 @@ in {
         "smbfs"
         "sockfs"
         "spufs"
-        "nfs"
-        "NFS"
-        "nfs4"
-        "nfsd"
         "sshfs"
         "subfs"
         "supermount"
         "sysfs"
         "tmpfs"
+        "tracefs"
         "ubifs"
+        "udev"
         "udf"
         "usbfs"
         "vboxsf"
@@ -149,7 +163,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
       '';
@@ -157,7 +171,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
       '';
@@ -202,7 +216,7 @@ in {
           PRUNEFS="${lib.concatStringsSep " " cfg.pruneFS}"
           PRUNENAMES="${lib.concatStringsSep " " cfg.pruneNames}"
           PRUNEPATHS="${lib.concatStringsSep " " cfg.prunePaths}"
-          PRUNE_BIND_MOUNTSFR="${lib.boolToString cfg.pruneBindMounts}"
+          PRUNE_BIND_MOUNTS="${if cfg.pruneBindMounts then "yes" else "no"}"
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/misc/man-db.nix b/nixpkgs/nixos/modules/misc/man-db.nix
new file mode 100644
index 000000000000..40ecf14d6a46
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/man-db.nix
@@ -0,0 +1,75 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.documentation.man.man-db;
+in
+
+{
+  options = {
+    documentation.man.man-db = {
+      enable = lib.mkEnableOption "man-db as the default man page viewer" // {
+        default = config.documentation.man.enable;
+        defaultText = lib.literalExpression "config.documentation.man.enable";
+        example = false;
+      };
+
+      manualPages = lib.mkOption {
+        type = lib.types.path;
+        default = pkgs.buildEnv {
+          name = "man-paths";
+          paths = config.environment.systemPackages;
+          pathsToLink = [ "/share/man" ];
+          extraOutputsToInstall = [ "man" ]
+            ++ lib.optionals config.documentation.dev.enable [ "devman" ];
+          ignoreCollisions = true;
+        };
+        defaultText = lib.literalDocBook "all man pages in <option>config.environment.systemPackages</option>";
+        description = ''
+          The manual pages to generate caches for if <option>documentation.man.generateCaches</option>
+          is enabled. Must be a path to a directory with man pages under
+          <literal>/share/man</literal>; see the source for an example.
+          Advanced users can make this a content-addressed derivation to save a few rebuilds.
+        '';
+      };
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.man-db;
+        defaultText = lib.literalExpression "pkgs.man-db";
+        description = ''
+          The <literal>man-db</literal> derivation to use. Useful to override
+          configuration options used for the package.
+        '';
+      };
+    };
+  };
+
+  imports = [
+    (lib.mkRenamedOptionModule [ "documentation" "man" "manualPages" ] [ "documentation" "man" "man-db" "manualPages" ])
+  ];
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    environment.etc."man_db.conf".text =
+      let
+        manualCache = pkgs.runCommandLocal "man-cache" { } ''
+          echo "MANDB_MAP ${cfg.manualPages}/share/man $out" > man.conf
+          ${cfg.package}/bin/mandb -C man.conf -psc >/dev/null 2>&1
+        '';
+      in
+      ''
+        # Manual pages paths for NixOS
+        MANPATH_MAP /run/current-system/sw/bin /run/current-system/sw/share/man
+        MANPATH_MAP /run/wrappers/bin          /run/current-system/sw/share/man
+
+        ${lib.optionalString config.documentation.man.generateCaches ''
+        # Generated manual pages cache for NixOS (immutable)
+        MANDB_MAP /run/current-system/sw/share/man ${manualCache}
+        ''}
+        # Manual pages caches for NixOS
+        MANDB_MAP /run/current-system/sw/share/man /var/cache/man/nixos
+
+        ${config.documentation.man.extraConfig}
+      '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/misc/mandoc.nix b/nixpkgs/nixos/modules/misc/mandoc.nix
new file mode 100644
index 000000000000..3da60f2f8e65
--- /dev/null
+++ b/nixpkgs/nixos/modules/misc/mandoc.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+let
+  makewhatis = "${lib.getBin cfg.package}/bin/makewhatis";
+
+  cfg = config.documentation.man.mandoc;
+
+in {
+  meta.maintainers = [ lib.maintainers.sternenseemann ];
+
+  options = {
+    documentation.man.mandoc = {
+      enable = lib.mkEnableOption "mandoc as the default man page viewer";
+
+      manPath = lib.mkOption {
+        type = with lib.types; listOf str;
+        default = [ "share/man" ];
+        example = lib.literalExpression "[ \"share/man\" \"share/man/fr\" ]";
+        description = ''
+          Change the manpath, i. e. the directories where
+          <citerefentry><refentrytitle>man</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+          looks for section-specific directories of man pages.
+          You only need to change this setting if you want extra man pages
+          (e. g. in non-english languages). All values must be strings that
+          are a valid path from the target prefix (without including it).
+          The first value given takes priority.
+        '';
+      };
+
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.mandoc;
+        defaultText = lib.literalExpression "pkgs.mandoc";
+        description = ''
+          The <literal>mandoc</literal> derivation to use. Useful to override
+          configuration options used for the package.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+
+      # tell mandoc about man pages
+      etc."man.conf".text = lib.concatMapStrings (path: ''
+        manpath /run/current-system/sw/${path}
+      '') cfg.manPath;
+
+      # create mandoc.db for whatis(1), apropos(1) and man(1) -k
+      # TODO(@sternenseemman): fix symlinked directories not getting indexed,
+      # see: https://inbox.vuxu.org/mandoc-tech/20210906171231.GF83680@athene.usta.de/T/#e85f773c1781e3fef85562b2794f9cad7b2909a3c
+      extraSetup = lib.mkIf config.documentation.man.generateCaches ''
+        ${makewhatis} -T utf8 ${
+          lib.concatMapStringsSep " " (path: "\"$out/${path}\"") cfg.manPath
+        }
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/misc/meta.nix b/nixpkgs/nixos/modules/misc/meta.nix
index 1410e33342a6..3dd97cbec235 100644
--- a/nixpkgs/nixos/modules/misc/meta.nix
+++ b/nixpkgs/nixos/modules/misc/meta.nix
@@ -37,7 +37,7 @@ in
         type = listOfMaintainers;
         internal = true;
         default = [];
-        example = [ lib.maintainers.all ];
+        example = literalExpression ''[ lib.maintainers.all ]'';
         description = ''
           List of maintainers of each module.  This option should be defined at
           most once per module.
diff --git a/nixpkgs/nixos/modules/misc/version.nix b/nixpkgs/nixos/modules/misc/version.nix
index 8f246a9278b7..fc0d65d5148e 100644
--- a/nixpkgs/nixos/modules/misc/version.nix
+++ b/nixpkgs/nixos/modules/misc/version.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.system.nixos;
+  opt = options.system.nixos;
 in
 
 {
@@ -53,6 +54,7 @@ in
     stateVersion = mkOption {
       type = types.str;
       default = cfg.release;
+      defaultText = literalExpression "config.${opt.release}";
       description = ''
         Every once in a while, a new NixOS release may change
         configuration defaults in a way incompatible with stateful
diff --git a/nixpkgs/nixos/modules/module-list.nix b/nixpkgs/nixos/modules/module-list.nix
index 2f01b04a0215..093c7c63a77a 100644
--- a/nixpkgs/nixos/modules/module-list.nix
+++ b/nixpkgs/nixos/modules/module-list.nix
@@ -45,9 +45,13 @@
   ./hardware/ckb-next.nix
   ./hardware/cpu/amd-microcode.nix
   ./hardware/cpu/intel-microcode.nix
+  ./hardware/cpu/intel-sgx.nix
   ./hardware/corectrl.nix
   ./hardware/digitalbitbox.nix
   ./hardware/device-tree.nix
+  ./hardware/gkraken.nix
+  ./hardware/flirc.nix
+  ./hardware/gpgsmartcards.nix
   ./hardware/i2c.nix
   ./hardware/sensor/hddtemp.nix
   ./hardware/sensor/iio.nix
@@ -104,6 +108,8 @@
   ./misc/lib.nix
   ./misc/label.nix
   ./misc/locate.nix
+  ./misc/man-db.nix
+  ./misc/mandoc.nix
   ./misc/meta.nix
   ./misc/nixpkgs.nix
   ./misc/passthru.nix
@@ -127,6 +133,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
@@ -291,7 +298,6 @@
   ./services/cluster/hadoop/default.nix
   ./services/cluster/k3s/default.nix
   ./services/cluster/kubernetes/addons/dns.nix
-  ./services/cluster/kubernetes/addons/dashboard.nix
   ./services/cluster/kubernetes/addon-manager.nix
   ./services/cluster/kubernetes/apiserver.nix
   ./services/cluster/kubernetes/controller-manager.nix
@@ -389,6 +395,7 @@
   ./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
@@ -420,6 +427,7 @@
   ./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
@@ -442,6 +450,7 @@
   ./services/hardware/xow.nix
   ./services/logging/SystemdJournal2Gelf.nix
   ./services/logging/awstats.nix
+  ./services/logging/filebeat.nix
   ./services/logging/fluentd.nix
   ./services/logging/graylog.nix
   ./services/logging/heartbeat.nix
@@ -463,6 +472,7 @@
   ./services/mail/dovecot.nix
   ./services/mail/dspam.nix
   ./services/mail/exim.nix
+  ./services/mail/maddy.nix
   ./services/mail/mail.nix
   ./services/mail/mailcatcher.nix
   ./services/mail/mailhog.nix
@@ -483,6 +493,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
@@ -500,7 +513,6 @@
   ./services/misc/cpuminer-cryptonight.nix
   ./services/misc/cgminer.nix
   ./services/misc/confd.nix
-  ./services/misc/couchpotato.nix
   ./services/misc/dendrite.nix
   ./services/misc/devmon.nix
   ./services/misc/dictd.nix
@@ -552,7 +564,6 @@
   ./services/misc/mediatomb.nix
   ./services/misc/metabase.nix
   ./services/misc/moonraker.nix
-  ./services/misc/mwlib.nix
   ./services/misc/mx-puppet-discord.nix
   ./services/misc/n8n.nix
   ./services/misc/nitter.nix
@@ -585,6 +596,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
@@ -604,6 +616,7 @@
   ./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
@@ -676,12 +689,15 @@
   ./services/network-filesystems/tahoe.nix
   ./services/network-filesystems/diod.nix
   ./services/network-filesystems/u9fs.nix
+  ./services/network-filesystems/webdav.nix
+  ./services/network-filesystems/webdav-server-rs.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
@@ -723,7 +739,6 @@
   ./services/networking/eternal-terminal.nix
   ./services/networking/fakeroute.nix
   ./services/networking/ferm.nix
-  ./services/networking/firefox/sync-server.nix
   ./services/networking/fireqos.nix
   ./services/networking/firewall.nix
   ./services/networking/flannel.nix
@@ -757,6 +772,7 @@
   ./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
@@ -768,6 +784,7 @@
   ./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
@@ -838,6 +855,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
@@ -884,6 +902,7 @@
   ./services/networking/unbound.nix
   ./services/networking/unifi.nix
   ./services/video/unifi-video.nix
+  ./services/video/rtsp-simple-server.nix
   ./services/networking/v2ray.nix
   ./services/networking/vsftpd.nix
   ./services/networking/wasabibackend.nix
@@ -911,6 +930,7 @@
   ./services/search/kibana.nix
   ./services/search/meilisearch.nix
   ./services/search/solr.nix
+  ./services/security/aesmd.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
   ./services/security/clamav.nix
@@ -968,6 +988,7 @@
   ./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
@@ -990,6 +1011,7 @@
   ./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
@@ -1000,16 +1022,17 @@
   ./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/powerdns-admin.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
   ./services/web-apps/tt-rss.nix
-  ./services/web-apps/trac.nix
   ./services/web-apps/trilium.nix
   ./services/web-apps/selfoss.nix
   ./services/web-apps/shiori.nix
@@ -1162,6 +1185,7 @@
   ./virtualisation/oci-containers.nix
   ./virtualisation/cri-o.nix
   ./virtualisation/docker.nix
+  ./virtualisation/docker-rootless.nix
   ./virtualisation/ecs-agent.nix
   ./virtualisation/libvirtd.nix
   ./virtualisation/lxc.nix
@@ -1172,14 +1196,14 @@
   ./virtualisation/kvmgt.nix
   ./virtualisation/openvswitch.nix
   ./virtualisation/parallels-guest.nix
-  ./virtualisation/podman.nix
-  ./virtualisation/podman-network-socket-ghostunnel.nix
+  ./virtualisation/podman/default.nix
   ./virtualisation/qemu-guest-agent.nix
   ./virtualisation/railcar.nix
   ./virtualisation/spice-usb-redirection.nix
   ./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/nixpkgs/nixos/modules/profiles/base.nix b/nixpkgs/nixos/modules/profiles/base.nix
index 3b67d628f9fd..33dd80d7c5ab 100644
--- a/nixpkgs/nixos/modules/profiles/base.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/profiles/minimal.nix b/nixpkgs/nixos/modules/profiles/minimal.nix
index f044e6f39ea5..e79b92723841 100644
--- a/nixpkgs/nixos/modules/profiles/minimal.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/bcc.nix b/nixpkgs/nixos/modules/programs/bcc.nix
index d76249bb5cab..e475c6ceaa6c 100644
--- a/nixpkgs/nixos/modules/programs/bcc.nix
+++ b/nixpkgs/nixos/modules/programs/bcc.nix
@@ -1,9 +1,9 @@
-{ config, lib, ... }:
+{ config, pkgs, lib, ... }:
 {
   options.programs.bcc.enable = lib.mkEnableOption "bcc";
 
   config = lib.mkIf config.programs.bcc.enable {
-    environment.systemPackages = [ config.boot.kernelPackages.bcc ];
-    boot.extraModulePackages = [ config.boot.kernelPackages.bcc ];
+    environment.systemPackages = [ pkgs.bcc ];
+    boot.extraModulePackages = [ pkgs.bcc ];
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/captive-browser.nix b/nixpkgs/nixos/modules/programs/captive-browser.nix
index 0f5d087e8d87..dc054504ea48 100644
--- a/nixpkgs/nixos/modules/programs/captive-browser.nix
+++ b/nixpkgs/nixos/modules/programs/captive-browser.nix
@@ -3,6 +3,18 @@
 with lib;
 let
   cfg = config.programs.captive-browser;
+  browserDefault = chromium: concatStringsSep " " [
+    ''env XDG_CONFIG_HOME="$PREV_CONFIG_HOME"''
+    ''${chromium}/bin/chromium''
+    ''--user-data-dir=''${XDG_DATA_HOME:-$HOME/.local/share}/chromium-captive''
+    ''--proxy-server="socks5://$PROXY"''
+    ''--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"''
+    ''--no-first-run''
+    ''--new-window''
+    ''--incognito''
+    ''-no-default-browser-check''
+    ''http://cache.nixos.org/''
+  ];
 in
 {
   ###### interface
@@ -26,18 +38,8 @@ in
       # the options below are the same as in "captive-browser.toml"
       browser = mkOption {
         type = types.str;
-        default = concatStringsSep " " [
-          ''env XDG_CONFIG_HOME="$PREV_CONFIG_HOME"''
-          ''${pkgs.chromium}/bin/chromium''
-          ''--user-data-dir=''${XDG_DATA_HOME:-$HOME/.local/share}/chromium-captive''
-          ''--proxy-server="socks5://$PROXY"''
-          ''--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"''
-          ''--no-first-run''
-          ''--new-window''
-          ''--incognito''
-          ''-no-default-browser-check''
-          ''http://cache.nixos.org/''
-        ];
+        default = browserDefault pkgs.chromium;
+        defaultText = literalExpression (browserDefault "\${pkgs.chromium}");
         description = ''
           The shell (/bin/sh) command executed once the proxy starts.
           When browser exits, the proxy exits. An extra env var PROXY is available.
diff --git a/nixpkgs/nixos/modules/programs/cnping.nix b/nixpkgs/nixos/modules/programs/cnping.nix
new file mode 100644
index 000000000000..d208d2b07040
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/file-roller.nix b/nixpkgs/nixos/modules/programs/file-roller.nix
index b939d59909c0..3c47d5981654 100644
--- a/nixpkgs/nixos/modules/programs/file-roller.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/programs/firejail.nix b/nixpkgs/nixos/modules/programs/firejail.nix
index 41db4f0136ef..8c10d7c4df39 100644
--- a/nixpkgs/nixos/modules/programs/firejail.nix
+++ b/nixpkgs/nixos/modules/programs/firejail.nix
@@ -74,8 +74,10 @@ in {
         </para>
         <para>
         You will get file collisions if you put the actual application binary in
-        the global environment and applications started via .desktop files are
-        not wrapped if they specify the absolute path to the binary.
+        the global environment (such as by adding the application package to
+        <code>environment.systemPackages</code>), and applications started via
+        .desktop files are not wrapped if they specify the absolute path to the
+        binary.
       '';
     };
   };
diff --git a/nixpkgs/nixos/modules/programs/gnupg.nix b/nixpkgs/nixos/modules/programs/gnupg.nix
index 06f49182e4df..fe5d7bd834b2 100644
--- a/nixpkgs/nixos/modules/programs/gnupg.nix
+++ b/nixpkgs/nixos/modules/programs/gnupg.nix
@@ -71,6 +71,7 @@ in
       type = types.nullOr (types.enum pkgs.pinentry.flavors);
       example = "gnome3";
       default = defaultPinentryFlavor;
+      defaultText = literalDocBook ''matching the configured desktop environment'';
       description = ''
         Which pinentry interface to use. If not null, the path to the
         pinentry binary will be passed to gpg-agent via commandline and
diff --git a/nixpkgs/nixos/modules/programs/neovim.nix b/nixpkgs/nixos/modules/programs/neovim.nix
index 97b77ae98fe2..4649662542de 100644
--- a/nixpkgs/nixos/modules/programs/neovim.nix
+++ b/nixpkgs/nixos/modules/programs/neovim.nix
@@ -41,7 +41,19 @@ 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 {
@@ -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/nixpkgs/nixos/modules/programs/qt5ct.nix b/nixpkgs/nixos/modules/programs/qt5ct.nix
index 3f2bcf622836..88e861bf4031 100644
--- a/nixpkgs/nixos/modules/programs/qt5ct.nix
+++ b/nixpkgs/nixos/modules/programs/qt5ct.nix
@@ -26,6 +26,6 @@ with lib;
   ###### implementation
   config = mkIf config.programs.qt5ct.enable {
     environment.variables.QT_QPA_PLATFORMTHEME = "qt5ct";
-    environment.systemPackages = with pkgs; [ qt5ct ];
+    environment.systemPackages = with pkgs; [ libsForQt5.qt5ct ];
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/ssh.nix b/nixpkgs/nixos/modules/programs/ssh.nix
index 5da15b68cf7d..c680063a47c3 100644
--- a/nixpkgs/nixos/modules/programs/ssh.nix
+++ b/nixpkgs/nixos/modules/programs/ssh.nix
@@ -33,6 +33,13 @@ in
 
     programs.ssh = {
 
+      enableAskPassword = mkOption {
+        type = types.bool;
+        default = config.services.xserver.enable;
+        defaultText = literalExpression "config.services.xserver.enable";
+        description = "Whether to configure SSH_ASKPASS in the environment.";
+      };
+
       askPassword = mkOption {
         type = types.str;
         default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass";
@@ -287,7 +294,7 @@ in
         # Allow ssh-agent to ask for confirmation. This requires the
         # unit to know about the user's $DISPLAY (via ‘systemctl
         # import-environment’).
-        environment.SSH_ASKPASS = optionalString config.services.xserver.enable askPasswordWrapper;
+        environment.SSH_ASKPASS = optionalString cfg.enableAskPassword askPasswordWrapper;
         environment.DISPLAY = "fake"; # required to make ssh-agent start $SSH_ASKPASS
       };
 
@@ -298,7 +305,7 @@ in
         fi
       '';
 
-    environment.variables.SSH_ASKPASS = optionalString config.services.xserver.enable askPassword;
+    environment.variables.SSH_ASKPASS = optionalString cfg.enableAskPassword askPassword;
 
   };
 }
diff --git a/nixpkgs/nixos/modules/programs/sway.nix b/nixpkgs/nixos/modules/programs/sway.nix
index caf329c2536a..c64e01a20cb3 100644
--- a/nixpkgs/nixos/modules/programs/sway.nix
+++ b/nixpkgs/nixos/modules/programs/sway.nix
@@ -123,6 +123,8 @@ in {
     ];
     environment = {
       systemPackages = [ swayPackage ] ++ cfg.extraPackages;
+      # Needed for the default wallpaper:
+      pathsToLink = [ "/share/backgrounds/sway" ];
       etc = {
         "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config";
         "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
index a8fcfff95e59..fee324cc7326 100644
--- a/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -36,6 +36,13 @@ in
       '';
     };
 
+    async = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to fetch suggestions asynchronously";
+      example = false;
+    };
+
     extraConfig = mkOption {
       type = with types; attrsOf str;
       default = {};
@@ -56,6 +63,7 @@ in
 
       export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="${cfg.highlightStyle}"
       export ZSH_AUTOSUGGEST_STRATEGY=("${cfg.strategy}")
+      ${optionalString (!cfg.async) "unset ZSH_AUTOSUGGEST_USE_ASYNC"}
 
       ${concatStringsSep "\n" (mapAttrsToList (key: value: ''export ${key}="${value}"'') cfg.extraConfig)}
     '';
diff --git a/nixpkgs/nixos/modules/programs/zsh/zsh.nix b/nixpkgs/nixos/modules/programs/zsh/zsh.nix
index dc6c958ca88b..5fe98b6801bb 100644
--- a/nixpkgs/nixos/modules/programs/zsh/zsh.nix
+++ b/nixpkgs/nixos/modules/programs/zsh/zsh.nix
@@ -1,6 +1,6 @@
 # This module defines global configuration for the zshell.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -9,6 +9,7 @@ let
   cfge = config.environment;
 
   cfg = config.programs.zsh;
+  opt = options.programs.zsh;
 
   zshAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
@@ -147,6 +148,7 @@ in
 
       enableGlobalCompInit = mkOption {
         default = cfg.enableCompletion;
+        defaultText = literalExpression "config.${opt.enableCompletion}";
         description = ''
           Enable execution of compinit call for all interactive zsh shells.
 
@@ -283,21 +285,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/nixpkgs/nixos/modules/rename.nix b/nixpkgs/nixos/modules/rename.nix
index 8e1d6f7bc4a5..0171a8511d5b 100644
--- a/nixpkgs/nixos/modules/rename.nix
+++ b/nixpkgs/nixos/modules/rename.nix
@@ -20,10 +20,11 @@ with lib;
     (mkRemovedOptionModule [ "fonts" "fontconfig" "penultimate" ] "The corresponding package has removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "quagga" ] "the corresponding package has been removed from nixpkgs")
     (mkRemovedOptionModule [ "services" "chronos" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "couchpotato" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "deepin" ] "The corresponding packages were removed from nixpkgs.")
-    (mkRemovedOptionModule [ "services" "firefox" "syncserver" "user" ] "")
-    (mkRemovedOptionModule [ "services" "firefox" "syncserver" "group" ] "")
+    (mkRemovedOptionModule [ "services" "firefox" "syncserver" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "marathon" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "moinmoin" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "mesos" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.")
@@ -37,6 +38,7 @@ with lib;
     (mkRemovedOptionModule [ "services" "frab" ] "The frab module has been removed")
     (mkRemovedOptionModule [ "services" "fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed")
     (mkRemovedOptionModule [ "services" "mathics" ] "The Mathics module has been removed")
+    (mkRemovedOptionModule [ "services" "mwlib" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "programs" "way-cooler" ] ("way-cooler is abandoned by its author: " +
       "https://way-cooler.org/blog/2020/01/09/way-cooler-post-mortem.html"))
     (mkRemovedOptionModule [ "services" "xserver" "multitouch" ] ''
diff --git a/nixpkgs/nixos/modules/security/acme.nix b/nixpkgs/nixos/modules/security/acme.nix
index f522b7c4128b..e244989d6408 100644
--- a/nixpkgs/nixos/modules/security/acme.nix
+++ b/nixpkgs/nixos/modules/security/acme.nix
@@ -2,6 +2,8 @@
 with lib;
 let
   cfg = config.security.acme;
+  opt = options.security.acme;
+  user = if cfg.useRoot then "root" else "acme";
 
   # Used to calculate timer accuracy for coalescing
   numCerts = length (builtins.attrNames cfg.certs);
@@ -22,7 +24,7 @@ let
   # security.acme.certs.<cert>.group on some of the services.
   commonServiceConfig = {
     Type = "oneshot";
-    User = "acme";
+    User = user;
     Group = mkDefault "acme";
     UMask = 0022;
     StateDirectoryMode = 750;
@@ -77,6 +79,7 @@ let
 
     unitConfig = {
       ConditionPathExists = "!/var/lib/acme/.minica/key.pem";
+      StartLimitIntervalSec = 0;
     };
 
     serviceConfig = commonServiceConfig // {
@@ -99,12 +102,12 @@ let
   # is configurable on a per-cert basis.
   userMigrationService = let
     script = with builtins; ''
-      chown -R acme .lego/accounts
+      chown -R ${user} .lego/accounts
     '' + (concatStringsSep "\n" (mapAttrsToList (cert: data: ''
       for fixpath in ${escapeShellArg cert} .lego/${escapeShellArg cert}; do
         if [ -d "$fixpath" ]; then
           chmod -R u=rwX,g=rX,o= "$fixpath"
-          chown -R acme:${data.group} "$fixpath"
+          chown -R ${user}:${data.group} "$fixpath"
         fi
       done
     '') certConfigs));
@@ -126,7 +129,7 @@ let
   };
 
   certToConfig = cert: data: let
-    acmeServer = if data.server != null then data.server else cfg.server;
+    acmeServer = data.server;
     useDns = data.dnsProvider != null;
     destPath = "/var/lib/acme/${cert}";
     selfsignedDeps = optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ];
@@ -154,6 +157,7 @@ let
       ${toString data.ocspMustStaple} ${data.keyType}
     '';
     certDir = mkHash hashData;
+    # TODO remove domainHash usage entirely. Waiting on go-acme/lego#1532
     domainHash = mkHash "${concatStringsSep " " extraDomains} ${data.domain}";
     accountHash = (mkAccountHash acmeServer data);
     accountDir = accountDirRoot + accountHash;
@@ -162,9 +166,8 @@ let
       [ "--dns" data.dnsProvider ]
       ++ optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ]
       ++ optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ]
-    ) else (
-      [ "--http" "--http.webroot" data.webroot ]
-    );
+    ) else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
+    else [ "--http" "--http.webroot" data.webroot ];
 
     commonOpts = [
       "--accept-tos" # Checking the option is covered by the assertions
@@ -192,6 +195,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;
 
@@ -201,7 +212,7 @@ let
       description = "Renew ACME Certificate for ${cert}";
       wantedBy = [ "timers.target" ];
       timerConfig = {
-        OnCalendar = cfg.renewInterval;
+        OnCalendar = data.renewInterval;
         Unit = "acme-${cert}.service";
         Persistent = "yes";
 
@@ -227,6 +238,7 @@ let
 
       unitConfig = {
         ConditionPathExists = "!/var/lib/acme/${cert}/key.pem";
+        StartLimitIntervalSec = 0;
       };
 
       serviceConfig = commonServiceConfig // {
@@ -257,7 +269,7 @@ let
         cat key.pem fullchain.pem > full.pem
 
         # Group might change between runs, re-apply it
-        chown 'acme:${data.group}' *
+        chown '${user}:${data.group}' *
 
         # Default permissions make the files unreadable by group + anon
         # Need to be readable by group
@@ -288,6 +300,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"
@@ -304,13 +318,19 @@ let
           if [ -e renewed ]; then
             rm renewed
             ${data.postRun}
+            ${optionalString (data.reloadServices != [])
+                "systemctl --no-block try-reload-or-restart ${escapeShellArgs data.reloadServices}"
+            }
           fi
         '');
+      } // optionalAttrs (data.listenHTTP != null && toInt (elemAt (splitString ":" data.listenHTTP) 1) < 1024) {
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
       };
 
       # Working directory will be /tmp
       script = ''
-        set -euxo pipefail
+        ${optionalString data.enableDebugLogs "set -x"}
+        set -euo pipefail
 
         # This reimplements the expiration date check, but without querying
         # the acme server first. By doing this offline, we avoid errors
@@ -337,7 +357,7 @@ let
           expiration_s=$[expiration_date - now]
           expiration_days=$[expiration_s / (3600 * 24)]   # rounds down
 
-          [[ $expiration_days -gt ${toString cfg.validMinDays} ]]
+          [[ $expiration_days -gt ${toString data.validMinDays} ]]
         }
 
         ${optionalString (data.webroot != null) ''
@@ -354,37 +374,40 @@ let
 
         echo '${domainHash}' > domainhash.txt
 
-        # Check if we can renew
-        if [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a -n "$(ls -1 accounts)" ]; then
+        # Check if we can renew.
+        # We can only renew if the list of domains has not changed.
+        if cmp -s domainhash.txt certificates/domainhash.txt && [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a -n "$(ls -1 accounts)" ]; then
 
-          # When domains are updated, there's no need to do a full
-          # Lego run, but it's likely renew won't work if days is too low.
-          if [ -e certificates/domainhash.txt ] && cmp -s domainhash.txt certificates/domainhash.txt; then
+          # Even if a cert is not expired, it may be revoked by the CA.
+          # Try to renew, and silently fail if the cert is not expired.
+          # Avoids #85794 and resolves #129838
+          if ! lego ${renewOpts} --days ${toString data.validMinDays}; then
             if is_expiration_skippable out/full.pem; then
-              echo 1>&2 "nixos-acme: skipping renewal because expiration isn't within the coming ${toString cfg.validMinDays} days"
+              echo 1>&2 "nixos-acme: Ignoring failed renewal because expiration isn't within the coming ${toString data.validMinDays} days"
             else
-              echo 1>&2 "nixos-acme: renewing now, because certificate expires within the configured ${toString cfg.validMinDays} days"
-              lego ${renewOpts} --days ${toString cfg.validMinDays}
+              # High number to avoid Systemd reserved codes.
+              exit 11
             fi
-          else
-            echo 1>&2 "certificate domain(s) have changed; will renew now"
-            # Any number > 90 works, but this one is over 9000 ;-)
-            lego ${renewOpts} --days 9001
           fi
 
         # Otherwise do a full run
-        else
-          lego ${runOpts}
+        elif ! lego ${runOpts}; then
+          # Produce a nice error for those doing their first nixos-rebuild with these certs
+          echo Failed to fetch certificates. \
+            This may mean your DNS records are set up incorrectly. \
+            ${optionalString (cfg.preliminarySelfsigned) "Selfsigned certs are in place and dependant services will still start."}
+          # Exit 10 so that users can potentially amend SuccessExitStatus to ignore this error.
+          # High number to avoid Systemd reserved codes.
+          exit 10
         fi
 
         mv domainhash.txt certificates/
 
         # Group might change between runs, re-apply it
-        chown 'acme:${data.group}' certificates/*
+        chown '${user}:${data.group}' certificates/*
 
         # Copy all certs to the "real" certs directory
-        CERT='certificates/${keyName}.crt'
-        if [ -e "$CERT" ] && ! cmp -s "$CERT" out/fullchain.pem; then
+        if ! cmp -s 'certificates/${keyName}.crt' out/fullchain.pem; then
           touch out/renewed
           echo Installing new certificate
           cp -vp 'certificates/${keyName}.crt' out/fullchain.pem
@@ -403,29 +426,45 @@ let
 
   certConfigs = mapAttrs certToConfig cfg.certs;
 
-  certOpts = { name, ... }: {
+  # These options can be specified within
+  # security.acme.defaults or security.acme.certs.<name>
+  inheritableModule = isDefaults: { config, ... }: let
+    defaultAndText = name: default: {
+      # When ! isDefaults then this is the option declaration for the
+      # security.acme.certs.<name> path, which has the extra inheritDefaults
+      # option, which if disabled means that we can't inherit it
+      default = if isDefaults || ! config.inheritDefaults then default else cfg.defaults.${name};
+      # The docs however don't need to depend on inheritDefaults, they should
+      # stay constant. Though notably it wouldn't matter much, because to get
+      # the option information, a submodule with name `<name>` is evaluated
+      # without any definitions.
+      defaultText = if isDefaults then default else literalExpression "config.security.acme.defaults.${name}";
+    };
+  in {
     options = {
-      # user option has been removed
-      user = mkOption {
-        visible = false;
-        default = "_mkRemovedOptionModule";
+      validMinDays = mkOption {
+        type = types.int;
+        inherit (defaultAndText "validMinDays" 30) default defaultText;
+        description = "Minimum remaining validity before renewal in days.";
       };
 
-      # allowKeysForGroup option has been removed
-      allowKeysForGroup = mkOption {
-        visible = false;
-        default = "_mkRemovedOptionModule";
+      renewInterval = mkOption {
+        type = types.str;
+        inherit (defaultAndText "renewInterval" "daily") default defaultText;
+        description = ''
+          Systemd calendar expression when to check for renewal. See
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
       };
 
-      # extraDomains was replaced with extraDomainNames
-      extraDomains = mkOption {
-        visible = false;
-        default = "_mkMergedOptionModule";
+      enableDebugLogs = mkEnableOption "debug logging for this certificate" // {
+        inherit (defaultAndText "enableDebugLogs" true) default defaultText;
       };
 
       webroot = mkOption {
         type = types.nullOr types.str;
-        default = null;
+        inherit (defaultAndText "webroot" null) default defaultText;
         example = "/var/lib/acme/acme-challenge";
         description = ''
           Where the webroot of the HTTP vhost is located.
@@ -438,7 +477,7 @@ let
 
       server = mkOption {
         type = types.nullOr types.str;
-        default = null;
+        inherit (defaultAndText "server" null) default defaultText;
         description = ''
           ACME Directory Resource URI. Defaults to Let's Encrypt's
           production endpoint,
@@ -446,27 +485,34 @@ let
         '';
       };
 
-      domain = mkOption {
-        type = types.str;
-        default = name;
-        description = "Domain to fetch certificate for (defaults to the entry name).";
-      };
-
       email = mkOption {
-        type = types.nullOr types.str;
-        default = cfg.email;
-        description = "Contact email address for the CA to be able to reach you.";
+        type = types.str;
+        inherit (defaultAndText "email" null) default defaultText;
+        description = ''
+          Email address for account creation and correspondence from the CA.
+          It is recommended to use the same email for all certs to avoid account
+          creation limits.
+        '';
       };
 
       group = mkOption {
         type = types.str;
-        default = "acme";
+        inherit (defaultAndText "group" "acme") default defaultText;
         description = "Group running the ACME client.";
       };
 
+      reloadServices = mkOption {
+        type = types.listOf types.str;
+        inherit (defaultAndText "reloadServices" []) default defaultText;
+        description = ''
+          The list of systemd services to call <code>systemctl try-reload-or-restart</code>
+          on.
+        '';
+      };
+
       postRun = mkOption {
         type = types.lines;
-        default = "";
+        inherit (defaultAndText "postRun" "") default defaultText;
         example = "cp full.pem backup.pem";
         description = ''
           Commands to run after new certificates go live. Note that
@@ -476,30 +522,9 @@ let
         '';
       };
 
-      directory = mkOption {
-        type = types.str;
-        readOnly = true;
-        default = "/var/lib/acme/${name}";
-        description = "Directory where certificate and other state is stored.";
-      };
-
-      extraDomainNames = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = literalExpression ''
-          [
-            "example.org"
-            "mydomain.org"
-          ]
-        '';
-        description = ''
-          A list of extra domain names, which are included in the one certificate to be issued.
-        '';
-      };
-
       keyType = mkOption {
         type = types.str;
-        default = "ec256";
+        inherit (defaultAndText "keyType" "ec256") default defaultText;
         description = ''
           Key type to use for private keys.
           For an up to date list of supported values check the --key-type option
@@ -509,7 +534,7 @@ let
 
       dnsProvider = mkOption {
         type = types.nullOr types.str;
-        default = null;
+        inherit (defaultAndText "dnsProvider" null) default defaultText;
         example = "route53";
         description = ''
           DNS Challenge provider. For a list of supported providers, see the "code"
@@ -519,7 +544,7 @@ let
 
       dnsResolver = mkOption {
         type = types.nullOr types.str;
-        default = null;
+        inherit (defaultAndText "dnsResolver" null) default defaultText;
         example = "1.1.1.1:53";
         description = ''
           Set the resolver to use for performing recursive DNS queries. Supported:
@@ -530,6 +555,7 @@ let
 
       credentialsFile = mkOption {
         type = types.path;
+        inherit (defaultAndText "credentialsFile" null) default defaultText;
         description = ''
           Path to an EnvironmentFile for the cert's service containing any required and
           optional environment variables for your selected dnsProvider.
@@ -541,7 +567,7 @@ let
 
       dnsPropagationCheck = mkOption {
         type = types.bool;
-        default = true;
+        inherit (defaultAndText "dnsPropagationCheck" true) default defaultText;
         description = ''
           Toggles lego DNS propagation check, which is used alongside DNS-01
           challenge to ensure the DNS entries required are available.
@@ -550,7 +576,7 @@ let
 
       ocspMustStaple = mkOption {
         type = types.bool;
-        default = false;
+        inherit (defaultAndText "ocspMustStaple" false) default defaultText;
         description = ''
           Turns on the OCSP Must-Staple TLS extension.
           Make sure you know what you're doing! See:
@@ -563,7 +589,7 @@ let
 
       extraLegoFlags = mkOption {
         type = types.listOf types.str;
-        default = [];
+        inherit (defaultAndText "extraLegoFlags" []) default defaultText;
         description = ''
           Additional global flags to pass to all lego commands.
         '';
@@ -571,7 +597,7 @@ let
 
       extraLegoRenewFlags = mkOption {
         type = types.listOf types.str;
-        default = [];
+        inherit (defaultAndText "extraLegoRenewFlags" []) default defaultText;
         description = ''
           Additional flags to pass to lego renew.
         '';
@@ -579,7 +605,7 @@ let
 
       extraLegoRunFlags = mkOption {
         type = types.listOf types.str;
-        default = [];
+        inherit (defaultAndText "extraLegoRunFlags" []) default defaultText;
         description = ''
           Additional flags to pass to lego run.
         '';
@@ -587,43 +613,80 @@ let
     };
   };
 
-in {
+  certOpts = { name, config, ... }: {
+    options = {
+      # user option has been removed
+      user = mkOption {
+        visible = false;
+        default = "_mkRemovedOptionModule";
+      };
 
-  options = {
-    security.acme = {
+      # allowKeysForGroup option has been removed
+      allowKeysForGroup = mkOption {
+        visible = false;
+        default = "_mkRemovedOptionModule";
+      };
 
-      validMinDays = mkOption {
-        type = types.int;
-        default = 30;
-        description = "Minimum remaining validity before renewal in days.";
+      # extraDomains was replaced with extraDomainNames
+      extraDomains = mkOption {
+        visible = false;
+        default = "_mkMergedOptionModule";
       };
 
-      email = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Contact email address for the CA to be able to reach you.";
+      directory = mkOption {
+        type = types.str;
+        readOnly = true;
+        default = "/var/lib/acme/${name}";
+        description = "Directory where certificate and other state is stored.";
       };
 
-      renewInterval = mkOption {
+      domain = mkOption {
         type = types.str;
-        default = "daily";
+        default = name;
+        description = "Domain to fetch certificate for (defaults to the entry name).";
+      };
+
+      extraDomainNames = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = literalExpression ''
+          [
+            "example.org"
+            "mydomain.org"
+          ]
+        '';
         description = ''
-          Systemd calendar expression when to check for renewal. See
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          A list of extra domain names, which are included in the one certificate to be issued.
         '';
       };
 
-      server = mkOption {
+      # This setting must be different for each configured certificate, otherwise
+      # two or more renewals may fail to bind to the address. Hence, it is not in
+      # the inheritableOpts.
+      listenHTTP = mkOption {
         type = types.nullOr types.str;
         default = null;
+        example = ":1360";
         description = ''
-          ACME Directory Resource URI. Defaults to Let's Encrypt's
-          production endpoint,
-          <link xlink:href="https://acme-v02.api.letsencrypt.org/directory"/>, if unset.
+          Interface and port to listen on to solve HTTP challenges
+          in the form [INTERFACE]:PORT.
+          If you use a port other than 80, you must proxy port 80 to this port.
         '';
       };
 
+      inheritDefaults = mkOption {
+        default = true;
+        example = true;
+        description = "Whether to inherit values set in `security.acme.defaults` or not.";
+        type = lib.types.bool;
+      };
+    };
+  };
+
+in {
+
+  options = {
+    security.acme = {
       preliminarySelfsigned = mkOption {
         type = types.bool;
         default = true;
@@ -646,9 +709,31 @@ in {
         '';
       };
 
+      useRoot = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use the root user when generating certs. This is not recommended
+          for security + compatiblity reasons. If a service requires root owned certificates
+          consider following the guide on "Using ACME with services demanding root
+          owned certificates" in the NixOS manual, and only using this as a fallback
+          or for testing.
+        '';
+      };
+
+      defaults = mkOption {
+        type = types.submodule (inheritableModule true);
+        description = ''
+          Default values inheritable by all configured certs. You can
+          use this to define options shared by all your certs. These defaults
+          can also be ignored on a per-cert basis using the
+          `security.acme.certs.''${cert}.inheritDefaults' option.
+        '';
+      };
+
       certs = mkOption {
         default = { };
-        type = with types; attrsOf (submodule certOpts);
+        type = with types; attrsOf (submodule [ (inheritableModule false) certOpts ]);
         description = ''
           Attribute set of certificates to get signed and renewed. Creates
           <literal>acme-''${cert}.{service,timer}</literal> systemd units for
@@ -679,12 +764,16 @@ in {
 
       To use the let's encrypt staging server, use security.acme.server =
       "https://acme-staging-v02.api.letsencrypt.org/directory".
-    ''
-    )
+    '')
     (mkRemovedOptionModule [ "security" "acme" "directory" ] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.")
     (mkRemovedOptionModule [ "security" "acme" "preDelay" ] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
     (mkRemovedOptionModule [ "security" "acme" "activationDelay" ] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
-    (mkChangedOptionModule [ "security" "acme" "validMin" ] [ "security" "acme" "validMinDays" ] (config: config.security.acme.validMin / (24 * 3600)))
+    (mkChangedOptionModule [ "security" "acme" "validMin" ] [ "security" "acme" "defaults" "validMinDays" ] (config: config.security.acme.validMin / (24 * 3600)))
+    (mkChangedOptionModule [ "security" "acme" "validMinDays" ] [ "security" "acme" "defaults" "validMinDays" ] (config: config.security.acme.validMinDays))
+    (mkChangedOptionModule [ "security" "acme" "renewInterval" ] [ "security" "acme" "defaults" "renewInterval" ] (config: config.security.acme.renewInterval))
+    (mkChangedOptionModule [ "security" "acme" "email" ] [ "security" "acme" "defaults" "email" ] (config: config.security.acme.email))
+    (mkChangedOptionModule [ "security" "acme" "server" ] [ "security" "acme" "defaults" "server" ] (config: config.security.acme.server))
+    (mkChangedOptionModule [ "security" "acme" "enableDebugLogs" ] [ "security" "acme" "defaults" "enableDebugLogs" ] (config: config.security.acme.enableDebugLogs))
   ];
 
   config = mkMerge [
@@ -754,6 +843,28 @@ in {
             `security.acme.certs.${cert}.webroot` are mutually exclusive.
           '';
         }
+        {
+          assertion = data.webroot == null || data.listenHTTP == null;
+          message = ''
+            Options `security.acme.certs.${cert}.webroot` and
+            `security.acme.certs.${cert}.listenHTTP` are mutually exclusive.
+          '';
+        }
+        {
+          assertion = data.listenHTTP == null || data.dnsProvider == null;
+          message = ''
+            Options `security.acme.certs.${cert}.listenHTTP` and
+            `security.acme.certs.${cert}.dnsProvider` are mutually exclusive.
+          '';
+        }
+        {
+          assertion = data.dnsProvider != null || data.webroot != null || data.listenHTTP != null;
+          message = ''
+            One of `security.acme.certs.${cert}.dnsProvider`,
+            `security.acme.certs.${cert}.webroot`, or
+            `security.acme.certs.${cert}.listenHTTP` must be provided.
+          '';
+        }
       ]) cfg.certs));
 
       users.users.acme = {
@@ -777,8 +888,8 @@ in {
         # Create some targets which can be depended on to be "active" after cert renewals
         finishedTargets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" {
           wantedBy = [ "default.target" ];
-          requires = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps;
-          after = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps;
+          requires = [ "acme-${cert}.service" ];
+          after = [ "acme-${cert}.service" ];
         }) certConfigs;
 
         # Create targets to limit the number of simultaneous account creations
diff --git a/nixpkgs/nixos/modules/security/acme.xml b/nixpkgs/nixos/modules/security/acme.xml
index 8249da948c6d..f623cc509be6 100644
--- a/nixpkgs/nixos/modules/security/acme.xml
+++ b/nixpkgs/nixos/modules/security/acme.xml
@@ -7,8 +7,9 @@
  <para>
   NixOS supports automatic domain validation &amp; certificate retrieval and
   renewal using the ACME protocol. Any provider can be used, but by default
-  NixOS uses Let's Encrypt. The alternative ACME client <literal>lego</literal>
-  is used under the hood.
+  NixOS uses Let's Encrypt. The alternative ACME client
+  <link xlink:href="https://go-acme.github.io/lego/">lego</link> is used under
+  the hood.
  </para>
  <para>
   Automatic cert validation and configuration for Apache and Nginx virtual
@@ -29,7 +30,7 @@
   <para>
    You must also set an email address to be used when creating accounts with
    Let's Encrypt. You can set this for all certs with
-   <literal><xref linkend="opt-security.acme.email" /></literal>
+   <literal><xref linkend="opt-security.acme.defaults.email" /></literal>
    and/or on a per-cert basis with
    <literal><xref linkend="opt-security.acme.certs._name_.email" /></literal>.
    This address is only used for registration and renewal reminders,
@@ -38,7 +39,7 @@
 
   <para>
    Alternatively, you can use a different ACME server by changing the
-   <literal><xref linkend="opt-security.acme.server" /></literal> option
+   <literal><xref linkend="opt-security.acme.defaults.server" /></literal> option
    to a provider of your choosing, or just change the server for one cert with
    <literal><xref linkend="opt-security.acme.certs._name_.server" /></literal>.
   </para>
@@ -60,12 +61,12 @@
    = true;</literal> in a virtualHost config. We first create self-signed
    placeholder certificates in place of the real ACME certs. The placeholder
    certs are overwritten when the ACME certs arrive. For
-   <literal>foo.example.com</literal> the config would look like.
+   <literal>foo.example.com</literal> the config would look like this:
   </para>
 
 <programlisting>
 <xref linkend="opt-security.acme.acceptTerms" /> = true;
-<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
+<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
 services.nginx = {
   <link linkend="opt-services.nginx.enable">enable</link> = true;
   <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
@@ -114,7 +115,7 @@ services.nginx = {
 
 <programlisting>
 <xref linkend="opt-security.acme.acceptTerms" /> = true;
-<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
+<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
 
 # /var/lib/acme/.challenges must be writable by the ACME user
 # and readable by the Nginx user. The easiest way to achieve
@@ -218,7 +219,7 @@ services.bind = {
 
 # Now we can configure ACME
 <xref linkend="opt-security.acme.acceptTerms" /> = true;
-<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
+<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
 <xref linkend="opt-security.acme.certs" />."example.com" = {
   <link linkend="opt-security.acme.certs._name_.domain">domain</link> = "*.example.com";
   <link linkend="opt-security.acme.certs._name_.dnsProvider">dnsProvider</link> = "rfc2136";
@@ -231,33 +232,147 @@ services.bind = {
   <para>
    The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename>
    must be kept secure and thus you should not keep their contents in your
-   Nix config. Instead, generate them one time with these commands:
+   Nix config. Instead, generate them one time with a systemd service:
   </para>
 
 <programlisting>
-mkdir -p /var/lib/secrets
-tsig-keygen rfc2136key.example.com &gt; /var/lib/secrets/dnskeys.conf
-chown named:root /var/lib/secrets/dnskeys.conf
-chmod 400 /var/lib/secrets/dnskeys.conf
-
-# Copy the secret value from the dnskeys.conf, and put it in
-# RFC2136_TSIG_SECRET below
-
-cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF
-RFC2136_NAMESERVER='127.0.0.1:53'
-RFC2136_TSIG_ALGORITHM='hmac-sha256.'
-RFC2136_TSIG_KEY='rfc2136key.example.com'
-RFC2136_TSIG_SECRET='your secret key'
-EOF
-chmod 400 /var/lib/secrets/certs.secret
+systemd.services.dns-rfc2136-conf = {
+  requiredBy = ["acme-example.com.service", "bind.service"];
+  before = ["acme-example.com.service", "bind.service"];
+  unitConfig = {
+    ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
+  };
+  serviceConfig = {
+    Type = "oneshot";
+    UMask = 0077;
+  };
+  path = [ pkgs.bind ];
+  script = ''
+    mkdir -p /var/lib/secrets
+    tsig-keygen rfc2136key.example.com &gt; /var/lib/secrets/dnskeys.conf
+    chown named:root /var/lib/secrets/dnskeys.conf
+    chmod 400 /var/lib/secrets/dnskeys.conf
+
+    # Copy the secret value from the dnskeys.conf, and put it in
+    # RFC2136_TSIG_SECRET below
+
+    cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF
+    RFC2136_NAMESERVER='127.0.0.1:53'
+    RFC2136_TSIG_ALGORITHM='hmac-sha256.'
+    RFC2136_TSIG_KEY='rfc2136key.example.com'
+    RFC2136_TSIG_SECRET='your secret key'
+    EOF
+    chmod 400 /var/lib/secrets/certs.secret
+  '';
+};
 </programlisting>
 
   <para>
-   Now you're all set to generate certs! You should monitor the first invokation
+   Now you're all set to generate certs! You should monitor the first invocation
    by running <literal>systemctl start acme-example.com.service &amp;
    journalctl -fu acme-example.com.service</literal> and watching its log output.
   </para>
  </section>
+
+ <section xml:id="module-security-acme-config-dns-with-vhosts">
+  <title>Using DNS validation with web server virtual hosts</title>
+
+  <para>
+   It is possible to use DNS-01 validation with all certificates,
+   including those automatically configured via the Nginx/Apache
+   <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link></literal>
+   option. This configuration pattern is fully
+   supported and part of the module's test suite for Nginx + Apache.
+  </para>
+
+  <para>
+   You must follow the guide above on configuring DNS-01 validation
+   first, however instead of setting the options for one certificate
+   (e.g. <xref linkend="opt-security.acme.certs._name_.dnsProvider" />)
+   you will set them as defaults
+   (e.g. <xref linkend="opt-security.acme.defaults.dnsProvider" />).
+  </para>
+
+<programlisting>
+# Configure ACME appropriately
+<xref linkend="opt-security.acme.acceptTerms" /> = true;
+<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
+<xref linkend="opt-security.acme.defaults" /> = {
+  <link linkend="opt-security.acme.defaults.dnsProvider">dnsProvider</link> = "rfc2136";
+  <link linkend="opt-security.acme.defaults.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret";
+  # We don't need to wait for propagation since this is a local DNS server
+  <link linkend="opt-security.acme.defaults.dnsPropagationCheck">dnsPropagationCheck</link> = false;
+};
+
+# For each virtual host you would like to use DNS-01 validation with,
+# set acmeRoot = null
+services.nginx = {
+  <link linkend="opt-services.nginx.enable">enable</link> = true;
+  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
+    "foo.example.com" = {
+      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+      <link linkend="opt-services.nginx.virtualHosts._name_.acmeRoot">acmeRoot</link> = null;
+    };
+  };
+}
+</programlisting>
+
+  <para>
+   And that's it! Next time your configuration is rebuilt, or when
+   you add a new virtualHost, it will be DNS-01 validated.
+  </para>
+ </section>
+
+ <section xml:id="module-security-acme-root-owned">
+  <title>Using ACME with services demanding root owned certificates</title>
+
+  <para>
+   Some services refuse to start if the configured certificate files
+   are not owned by root. PostgreSQL and OpenSMTPD are examples of these.
+   There is no way to change the user the ACME module uses (it will always be
+   <literal>acme</literal>), however you can use systemd's
+   <literal>LoadCredential</literal> feature to resolve this elegantly.
+   Below is an example configuration for OpenSMTPD, but this pattern
+   can be applied to any service.
+  </para>
+
+<programlisting>
+# Configure ACME however you like (DNS or HTTP validation), adding
+# the following configuration for the relevant certificate.
+# Note: You cannot use `systemctl reload` here as that would mean
+# the LoadCredential configuration below would be skipped and
+# the service would continue to use old certificates.
+security.acme.certs."mail.example.com".postRun = ''
+  systemctl restart opensmtpd
+'';
+
+# Now you must augment OpenSMTPD's systemd service to load
+# the certificate files.
+<link linkend="opt-systemd.services._name_.requires">systemd.services.opensmtpd.requires</link> = ["acme-finished-mail.example.com.target"];
+<link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.opensmtpd.serviceConfig.LoadCredential</link> = let
+  certDir = config.security.acme.certs."mail.example.com".directory;
+in [
+  "cert.pem:${certDir}/cert.pem"
+  "key.pem:${certDir}/key.pem"
+];
+
+# Finally, configure OpenSMTPD to use these certs.
+services.opensmtpd = let
+  credsDir = "/run/credentials/opensmtpd.service";
+in {
+  enable = true;
+  setSendmail = false;
+  serverConfiguration = ''
+    pki mail.example.com cert "${credsDir}/cert.pem"
+    pki mail.example.com key "${credsDir}/key.pem"
+    listen on localhost tls pki mail.example.com
+    action act1 relay host smtp://127.0.0.1:10027
+    match for local action act1
+  '';
+};
+</programlisting>
+ </section>
+
  <section xml:id="module-security-acme-regenerate">
   <title>Regenerating certificates</title>
 
diff --git a/nixpkgs/nixos/modules/security/ca.nix b/nixpkgs/nixos/modules/security/ca.nix
index 83c15f90f92e..f71d9d90ec5b 100644
--- a/nixpkgs/nixos/modules/security/ca.nix
+++ b/nixpkgs/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
 
@@ -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/nixpkgs/nixos/modules/security/dhparams.nix b/nixpkgs/nixos/modules/security/dhparams.nix
index 012be2887d89..cfa9003f12fb 100644
--- a/nixpkgs/nixos/modules/security/dhparams.nix
+++ b/nixpkgs/nixos/modules/security/dhparams.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
-  inherit (lib) mkOption types;
+  inherit (lib) literalExpression mkOption types;
   cfg = config.security.dhparams;
+  opt = options.security.dhparams;
 
   bitType = types.addCheck types.int (b: b >= 16) // {
     name = "bits";
@@ -13,6 +14,7 @@ let
     options.bits = mkOption {
       type = bitType;
       default = cfg.defaultBitSize;
+      defaultText = literalExpression "config.${opt.defaultBitSize}";
       description = ''
         The bit size for the prime that is used during a Diffie-Hellman
         key exchange.
diff --git a/nixpkgs/nixos/modules/security/doas.nix b/nixpkgs/nixos/modules/security/doas.nix
index 9a3daf4f504c..2a814f17e454 100644
--- a/nixpkgs/nixos/modules/security/doas.nix
+++ b/nixpkgs/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:
diff --git a/nixpkgs/nixos/modules/security/pam.nix b/nixpkgs/nixos/modules/security/pam.nix
index 4c18fa8cc67f..0944b36c6d19 100644
--- a/nixpkgs/nixos/modules/security/pam.nix
+++ b/nixpkgs/nixos/modules/security/pam.nix
@@ -38,6 +38,7 @@ let
 
       p11Auth = mkOption {
         default = config.security.pam.p11.enable;
+        defaultText = literalExpression "config.security.pam.p11.enable";
         type = types.bool;
         description = ''
           If set, keys listed in
@@ -49,6 +50,7 @@ let
 
       u2fAuth = mkOption {
         default = config.security.pam.u2f.enable;
+        defaultText = literalExpression "config.security.pam.u2f.enable";
         type = types.bool;
         description = ''
           If set, users listed in
@@ -61,6 +63,7 @@ let
 
       yubicoAuth = mkOption {
         default = config.security.pam.yubico.enable;
+        defaultText = literalExpression "config.security.pam.yubico.enable";
         type = types.bool;
         description = ''
           If set, users listed in
@@ -83,6 +86,7 @@ let
 
       usbAuth = mkOption {
         default = config.security.pam.usb.enable;
+        defaultText = literalExpression "config.security.pam.usb.enable";
         type = types.bool;
         description = ''
           If set, users listed in
@@ -93,6 +97,7 @@ let
 
       otpwAuth = mkOption {
         default = config.security.pam.enableOTPW;
+        defaultText = literalExpression "config.security.pam.enableOTPW";
         type = types.bool;
         description = ''
           If set, the OTPW system will be used (if
@@ -126,6 +131,7 @@ let
 
       fprintAuth = mkOption {
         default = config.services.fprintd.enable;
+        defaultText = literalExpression "config.services.fprintd.enable";
         type = types.bool;
         description = ''
           If set, fingerprint reader will be used (if exists and
@@ -135,6 +141,7 @@ let
 
       oathAuth = mkOption {
         default = config.security.pam.oath.enable;
+        defaultText = literalExpression "config.security.pam.oath.enable";
         type = types.bool;
         description = ''
           If set, the OATH Toolkit will be used.
@@ -197,6 +204,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;
@@ -209,6 +256,7 @@ let
 
       pamMount = mkOption {
         default = config.security.pam.mount.enable;
+        defaultText = literalExpression "config.security.pam.mount.enable";
         type = types.bool;
         description = ''
           Enable PAM mount (pam_mount) system to mount fileystems on user login.
@@ -247,9 +295,14 @@ let
       };
 
       limits = mkOption {
+        default = [];
+        type = limitsType;
         description = ''
           Attribute set describing resource limits.  Defaults to the
           value of <option>security.pam.loginLimits</option>.
+          The meaning of the values is explained in <citerefentry>
+          <refentrytitle>limits.conf</refentrytitle><manvolnum>5</manvolnum>
+          </citerefentry>.
         '';
       };
 
@@ -361,6 +414,9 @@ let
 
     };
 
+    # The resulting /etc/pam.d/* file contents are verified in
+    # nixos/tests/pam/pam-file-contents.nix. Please update tests there when
+    # changing the derivation.
     config = {
       name = mkDefault name;
       setLoginUid = mkDefault cfg.startSession;
@@ -370,46 +426,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 +491,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
+          ''
+        );
     };
 
   };
@@ -537,6 +653,51 @@ let
          "${domain} ${type} ${item} ${toString value}\n")
          limits);
 
+  limitsType = with lib.types; listOf (submodule ({ ... }: {
+    options = {
+      domain = mkOption {
+        description = "Username, groupname, or wildcard this limit applies to";
+        example = "@wheel";
+        type = str;
+      };
+
+      type = mkOption {
+        description = "Type of this limit";
+        type = enum [ "-" "hard" "soft" ];
+        default = "-";
+      };
+
+      item = mkOption {
+        description = "Item this limit applies to";
+        type = enum [
+          "core"
+          "data"
+          "fsize"
+          "memlock"
+          "nofile"
+          "rss"
+          "stack"
+          "cpu"
+          "nproc"
+          "as"
+          "maxlogins"
+          "maxsyslogins"
+          "priority"
+          "locks"
+          "sigpending"
+          "msgqueue"
+          "nice"
+          "rtprio"
+        ];
+      };
+
+      value = mkOption {
+        description = "Value of this limit";
+        type = oneOf [ str int ];
+      };
+    };
+  }));
+
   motd = pkgs.writeText "motd" config.users.motd;
 
   makePAMService = name: service:
@@ -558,6 +719,7 @@ in
 
     security.pam.loginLimits = mkOption {
       default = [];
+      type = limitsType;
       example =
         [ { domain = "ftp";
             type   = "hard";
@@ -577,7 +739,8 @@ in
           <varname>domain</varname>, <varname>type</varname>,
           <varname>item</varname>, and <varname>value</varname>
           attribute.  The syntax and semantics of these attributes
-          must be that described in the limits.conf(5) man page.
+          must be that described in <citerefentry><refentrytitle>limits.conf</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry>.
 
           Note that these limits do not apply to systemd services,
           whose limits can be changed via <option>systemd.extraConfig</option>
diff --git a/nixpkgs/nixos/modules/security/systemd-confinement.nix b/nixpkgs/nixos/modules/security/systemd-confinement.nix
index d859c45c74f7..0e3ec5af323e 100644
--- a/nixpkgs/nixos/modules/security/systemd-confinement.nix
+++ b/nixpkgs/nixos/modules/security/systemd-confinement.nix
@@ -1,11 +1,9 @@
-{ config, pkgs, lib, ... }:
+{ config, pkgs, lib, utils, ... }:
 
 let
   toplevelConfig = config;
   inherit (lib) types;
-  inherit (import ../system/boot/systemd-lib.nix {
-    inherit config pkgs lib;
-  }) mkPathSafeName;
+  inherit (utils.systemdUtils.lib) mkPathSafeName;
 in {
   options.systemd.services = lib.mkOption {
     type = types.attrsOf (types.submodule ({ name, config, ... }: {
diff --git a/nixpkgs/nixos/modules/security/wrappers/default.nix b/nixpkgs/nixos/modules/security/wrappers/default.nix
index a47de7e04f7a..66a47bcaab6c 100644
--- a/nixpkgs/nixos/modules/security/wrappers/default.nix
+++ b/nixpkgs/nixos/modules/security/wrappers/default.nix
@@ -244,8 +244,6 @@ in
     security.apparmor.includes."nixos/security.wrappers" = ''
       include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [
         securityWrapper
-        pkgs.stdenv.cc.cc
-        pkgs.stdenv.cc.libc
       ]}"
     '';
 
diff --git a/nixpkgs/nixos/modules/services/audio/icecast.nix b/nixpkgs/nixos/modules/services/audio/icecast.nix
index 6ca20a7a1086..5ee5bd745f96 100644
--- a/nixpkgs/nixos/modules/services/audio/icecast.nix
+++ b/nixpkgs/nixos/modules/services/audio/icecast.nix
@@ -50,6 +50,7 @@ in {
         type = types.nullOr types.str;
         description = "DNS name or IP address that will be used for the stream directory lookups or possibily the playlist generation if a Host header is not provided.";
         default = config.networking.domain;
+        defaultText = literalExpression "config.networking.domain";
       };
 
       admin = {
diff --git a/nixpkgs/nixos/modules/services/audio/mpdscribble.nix b/nixpkgs/nixos/modules/services/audio/mpdscribble.nix
index 1368543ae1a4..333ffb709410 100644
--- a/nixpkgs/nixos/modules/services/audio/mpdscribble.nix
+++ b/nixpkgs/nixos/modules/services/audio/mpdscribble.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.mpdscribble;
   mpdCfg = config.services.mpd;
+  mpdOpt = options.services.mpd;
 
   endpointUrls = {
     "last.fm" = "http://post.audioscrobbler.com";
@@ -108,6 +109,11 @@ in {
         mpdCfg.network.listenAddress
       else
         "localhost");
+      defaultText = literalExpression ''
+        if config.${mpdOpt.network.listenAddress} != "any"
+        then config.${mpdOpt.network.listenAddress}
+        else "localhost"
+      '';
       type = types.str;
       description = ''
         Host for the mpdscribble daemon to search for a mpd daemon on.
@@ -122,6 +128,10 @@ in {
           mpdCfg.credentials).passwordFile
       else
         null;
+      defaultText = literalDocBook ''
+        The first password file with read access configured for MPD when using a local instance,
+        otherwise <literal>null</literal>.
+      '';
       type = types.nullOr types.str;
       description = ''
         File containing the password for the mpd daemon.
@@ -132,6 +142,7 @@ in {
 
     port = mkOption {
       default = mpdCfg.network.port;
+      defaultText = literalExpression "config.${mpdOpt.network.port}";
       type = types.port;
       description = ''
         Port for the mpdscribble daemon to search for a mpd daemon on.
diff --git a/nixpkgs/nixos/modules/services/audio/navidrome.nix b/nixpkgs/nixos/modules/services/audio/navidrome.nix
index c2fe429f9844..3660e05310be 100644
--- a/nixpkgs/nixos/modules/services/audio/navidrome.nix
+++ b/nixpkgs/nixos/modules/services/audio/navidrome.nix
@@ -9,7 +9,7 @@ in {
   options = {
     services.navidrome = {
 
-      enable = mkEnableOption pkgs.navidrome.meta.description;
+      enable = mkEnableOption "Navidrome music server";
 
       settings = mkOption rec {
         type = settingsFormat.type;
diff --git a/nixpkgs/nixos/modules/services/audio/roon-server.nix b/nixpkgs/nixos/modules/services/audio/roon-server.nix
index 42da5a100170..566c7cae42ce 100644
--- a/nixpkgs/nixos/modules/services/audio/roon-server.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/audio/snapserver.nix b/nixpkgs/nixos/modules/services/audio/snapserver.nix
index d3e97719f357..b82aca3976f0 100644
--- a/nixpkgs/nixos/modules/services/audio/snapserver.nix
+++ b/nixpkgs/nixos/modules/services/audio/snapserver.nix
@@ -54,12 +54,12 @@ let
     # tcp json rpc
     ++ [ "--tcp.enabled ${toString cfg.tcp.enable}" ]
     ++ optionals cfg.tcp.enable [
-      "--tcp.address ${cfg.tcp.listenAddress}"
+      "--tcp.bind_to_address ${cfg.tcp.listenAddress}"
       "--tcp.port ${toString cfg.tcp.port}" ]
      # http json rpc
     ++ [ "--http.enabled ${toString cfg.http.enable}" ]
     ++ optionals cfg.http.enable [
-      "--http.address ${cfg.http.listenAddress}"
+      "--http.bind_to_address ${cfg.http.listenAddress}"
       "--http.port ${toString cfg.http.port}"
     ] ++ optional (cfg.http.docRoot != null) "--http.doc_root \"${toString cfg.http.docRoot}\"");
 
diff --git a/nixpkgs/nixos/modules/services/audio/ympd.nix b/nixpkgs/nixos/modules/services/audio/ympd.nix
index 36c5527027ff..84b72d142513 100644
--- a/nixpkgs/nixos/modules/services/audio/ympd.nix
+++ b/nixpkgs/nixos/modules/services/audio/ympd.nix
@@ -31,6 +31,7 @@ in {
         port = mkOption {
           type = types.int;
           default = config.services.mpd.network.port;
+          defaultText = literalExpression "config.services.mpd.network.port";
           description = "The port where MPD is listening.";
           example = 6600;
         };
diff --git a/nixpkgs/nixos/modules/services/backup/bacula.nix b/nixpkgs/nixos/modules/services/backup/bacula.nix
index cc8b77cbfbe8..598902042346 100644
--- a/nixpkgs/nixos/modules/services/backup/bacula.nix
+++ b/nixpkgs/nixos/modules/services/backup/bacula.nix
@@ -302,6 +302,7 @@ in {
 
       name = mkOption {
         default = "${config.networking.hostName}-fd";
+        defaultText = literalExpression ''"''${config.networking.hostName}-fd"'';
         type = types.str;
         description = ''
           The client name that must be used by the Director when connecting.
@@ -364,6 +365,7 @@ in {
 
       name = mkOption {
         default = "${config.networking.hostName}-sd";
+        defaultText = literalExpression ''"''${config.networking.hostName}-sd"'';
         type = types.str;
         description = ''
           Specifies the Name of the Storage daemon.
@@ -439,6 +441,7 @@ in {
 
       name = mkOption {
         default = "${config.networking.hostName}-dir";
+        defaultText = literalExpression ''"''${config.networking.hostName}-dir"'';
         type = types.str;
         description = ''
           The director name used by the system administrator. This directive is
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.nix b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
index 5461dbaf0bd0..220c571b927e 100644
--- a/nixpkgs/nixos/modules/services/backup/borgbackup.nix
+++ b/nixpkgs/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"
@@ -182,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 = ''
@@ -240,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.";
@@ -657,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/nixpkgs/nixos/modules/services/backup/duplicati.nix b/nixpkgs/nixos/modules/services/backup/duplicati.nix
index cf5aebdecd28..97864c44691b 100644
--- a/nixpkgs/nixos/modules/services/backup/duplicati.nix
+++ b/nixpkgs/nixos/modules/services/backup/duplicati.nix
@@ -18,6 +18,20 @@ in
         '';
       };
 
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/duplicati";
+        description = ''
+          The directory where Duplicati stores its data files.
+
+          <note><para>
+            If left as the default value this directory will automatically be created
+            before the Duplicati server starts, otherwise you are responsible for ensuring
+            the directory exists with appropriate ownership and permissions.
+          </para></note>
+        '';
+      };
+
       interface = mkOption {
         default = "127.0.0.1";
         type = types.str;
@@ -45,20 +59,23 @@ in
       description = "Duplicati backup";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        User = cfg.user;
-        Group = "duplicati";
-        StateDirectory = "duplicati";
-        ExecStart = "${pkgs.duplicati}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=/var/lib/duplicati";
-        Restart = "on-failure";
-      };
+      serviceConfig = mkMerge [
+        {
+          User = cfg.user;
+          Group = "duplicati";
+          ExecStart = "${pkgs.duplicati}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=${cfg.dataDir}";
+          Restart = "on-failure";
+        }
+        (mkIf (cfg.dataDir == "/var/lib/duplicati") {
+          StateDirectory = "duplicati";
+        })
+      ];
     };
 
     users.users = lib.optionalAttrs (cfg.user == "duplicati") {
       duplicati = {
         uid = config.ids.uids.duplicati;
-        home = "/var/lib/duplicati";
-        createHome = true;
+        home = cfg.dataDir;
         group = "duplicati";
       };
     };
diff --git a/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
index 86744637f85d..4717119f178a 100644
--- a/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
@@ -95,6 +95,10 @@ in
       };
     };
 
+    systemd.tmpfiles.rules = mkIf cfg.privateRepos [
+        "f ${cfg.dataDir}/.htpasswd 0700 restic restic -"
+    ];
+
     users.users.restic = {
       group = "restic";
       home = cfg.dataDir;
diff --git a/nixpkgs/nixos/modules/services/backup/restic.nix b/nixpkgs/nixos/modules/services/backup/restic.nix
index ac57f271526f..8ff8e31864be 100644
--- a/nixpkgs/nixos/modules/services/backup/restic.nix
+++ b/nixpkgs/nixos/modules/services/backup/restic.nix
@@ -1,17 +1,17 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
 let
   # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers"
-  unitOption = (import ../../system/boot/systemd-unit-options.nix { inherit config lib; }).unitOption;
+  inherit (utils.systemdUtils.unitOptions) unitOption;
 in
 {
   options.services.restic.backups = mkOption {
     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/nixpkgs/nixos/modules/services/backup/tarsnap.nix b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
index 9cce86836612..9b5fd90012e0 100644
--- a/nixpkgs/nixos/modules/services/backup/tarsnap.nix
+++ b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, options, pkgs, utils, ... }:
 
 with lib;
 
 let
   gcfg = config.services.tarsnap;
+  opt = options.services.tarsnap;
 
   configFile = name: cfg: ''
     keyfile ${cfg.keyfile}
@@ -59,12 +60,13 @@ in
       };
 
       archives = mkOption {
-        type = types.attrsOf (types.submodule ({ config, ... }:
+        type = types.attrsOf (types.submodule ({ config, options, ... }:
           {
             options = {
               keyfile = mkOption {
                 type = types.str;
                 default = gcfg.keyfile;
+                defaultText = literalExpression "config.${opt.keyfile}";
                 description = ''
                   Set a specific keyfile for this archive. This defaults to
                   <literal>"/root/tarsnap.key"</literal> if left unspecified.
@@ -87,6 +89,9 @@ in
               cachedir = mkOption {
                 type = types.nullOr types.path;
                 default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}";
+                defaultText = literalExpression ''
+                  "/var/cache/tarsnap/''${utils.escapeSystemdPath config.${options.keyfile}}"
+                '';
                 description = ''
                   The cache allows tarsnap to identify previously stored data
                   blocks, reducing archival time and bandwidth usage.
@@ -320,21 +325,22 @@ in
                         ${optionalString cfg.explicitSymlinks "-H"} \
                         ${optionalString cfg.followSymlinks "-L"} \
                         ${concatStringsSep " " cfg.directories}'';
+          cachedir = escapeShellArg cfg.cachedir;
           in if (cfg.cachedir != null) then ''
-            mkdir -p ${cfg.cachedir}
-            chmod 0700 ${cfg.cachedir}
+            mkdir -p ${cachedir}
+            chmod 0700 ${cachedir}
 
             ( flock 9
-              if [ ! -e ${cfg.cachedir}/firstrun ]; then
+              if [ ! -e ${cachedir}/firstrun ]; then
                 ( flock 10
                   flock -u 9
                   ${tarsnap} --fsck
                   flock 9
-                ) 10>${cfg.cachedir}/firstrun
+                ) 10>${cachedir}/firstrun
               fi
-            ) 9>${cfg.cachedir}/lockf
+            ) 9>${cachedir}/lockf
 
-             exec flock ${cfg.cachedir}/firstrun ${run}
+             exec flock ${cachedir}/firstrun ${run}
           '' else "exec ${run}";
 
         serviceConfig = {
@@ -356,22 +362,23 @@ in
           tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
           lastArchive = "$(${tarsnap} --list-archives | sort | tail -1)";
           run = ''${tarsnap} -x -f "${lastArchive}" ${optionalString cfg.verbose "-v"}'';
+          cachedir = escapeShellArg cfg.cachedir;
 
         in if (cfg.cachedir != null) then ''
-          mkdir -p ${cfg.cachedir}
-          chmod 0700 ${cfg.cachedir}
+          mkdir -p ${cachedir}
+          chmod 0700 ${cachedir}
 
           ( flock 9
-            if [ ! -e ${cfg.cachedir}/firstrun ]; then
+            if [ ! -e ${cachedir}/firstrun ]; then
               ( flock 10
                 flock -u 9
                 ${tarsnap} --fsck
                 flock 9
-              ) 10>${cfg.cachedir}/firstrun
+              ) 10>${cachedir}/firstrun
             fi
-          ) 9>${cfg.cachedir}/lockf
+          ) 9>${cachedir}/lockf
 
-           exec flock ${cfg.cachedir}/firstrun ${run}
+           exec flock ${cachedir}/firstrun ${run}
         '' else "exec ${run}";
 
         serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix
index 38db10406b9a..0caec5cfc203 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
index a165f619dc0c..a1a95fe31cac 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
@@ -1,5 +1,8 @@
-{ config, lib, pkgs, ...}:
-
+{ config, lib, options, pkgs, ...}:
+let
+  cfg = config.services.hadoop;
+  opt = options.services.hadoop;
+in
 with lib;
 {
   imports = [ ./yarn.nix ./hdfs.nix ];
@@ -13,40 +16,136 @@ with lib;
           "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 = 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}";
+      };
+      defaultText = literalExpression ''
+        {
+          "mapreduce.framework.name" = "yarn";
+          "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
+          "mapreduce.map.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
+          "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
+        }
+      '';
       type = types.attrsOf types.anything;
       example = literalExpression ''
-        {
-          "mapreduce.map.cpu.vcores" = "1";
+        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 = 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";
+      defaultText = literalExpression ''
+        "''${config.${opt.package}}/lib/''${config.${opt.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 {
@@ -60,10 +159,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/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
index 4f4b0a92108f..be667aa82d8a 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
index c92020637e47..37c26ea10f76 100644
--- a/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index 3d988dc2479a..9159d5915eb7 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -58,7 +58,7 @@ in
             "spec" = { ... };
           };
         }
-        // import <nixpkgs/nixos/modules/services/cluster/kubernetes/dashboard.nix> { cfg = config.services.kubernetes; };
+        // import <nixpkgs/nixos/modules/services/cluster/kubernetes/dns.nix> { cfg = config.services.kubernetes; };
       '';
     };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
deleted file mode 100644
index 2ed7742eda09..000000000000
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
+++ /dev/null
@@ -1,332 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.kubernetes.addons.dashboard;
-in {
-  imports = [
-    (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ])
-  ];
-
-  options.services.kubernetes.addons.dashboard = {
-    enable = mkEnableOption "kubernetes dashboard addon";
-
-    extraArgs = mkOption {
-      description = "Extra arguments to append to the dashboard cmdline";
-      type = types.listOf types.str;
-      default = [];
-      example = ["--enable-skip-login"];
-    };
-
-    rbac = mkOption {
-      description = "Role-based access control (RBAC) options";
-      default = {};
-      type = types.submodule {
-        options = {
-          enable = mkOption {
-            description = "Whether to enable role based access control is enabled for kubernetes dashboard";
-            type = types.bool;
-            default = elem "RBAC" config.services.kubernetes.apiserver.authorizationMode;
-          };
-
-          clusterAdmin = mkOption {
-            description = "Whether to assign cluster admin rights to the kubernetes dashboard";
-            type = types.bool;
-            default = false;
-          };
-        };
-      };
-    };
-
-    version = mkOption {
-      description = "Which version of the kubernetes dashboard to deploy";
-      type = types.str;
-      default = "v1.10.1";
-    };
-
-    image = mkOption {
-      description = "Docker image to seed for the kubernetes dashboard container.";
-      type = types.attrs;
-      default = {
-        imageName = "k8s.gcr.io/kubernetes-dashboard-amd64";
-        imageDigest = "sha256:0ae6b69432e78069c5ce2bcde0fe409c5c4d6f0f4d9cd50a17974fea38898747";
-        finalImageTag = cfg.version;
-        sha256 = "01xrr4pwgr2hcjrjsi3d14ifpzdfbxzqpzxbk2fkbjb9zkv38zxy";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.kubernetes.kubelet.seedDockerImages = [(pkgs.dockerTools.pullImage cfg.image)];
-
-    services.kubernetes.addonManager.addons = {
-      kubernetes-dashboard-deployment = {
-        kind = "Deployment";
-        apiVersion = "apps/v1";
-        metadata = {
-          labels = {
-            k8s-addon = "kubernetes-dashboard.addons.k8s.io";
-            k8s-app = "kubernetes-dashboard";
-            version = cfg.version;
-            "kubernetes.io/cluster-service" = "true";
-            "addonmanager.kubernetes.io/mode" = "Reconcile";
-          };
-          name = "kubernetes-dashboard";
-          namespace = "kube-system";
-        };
-        spec = {
-          replicas = 1;
-          revisionHistoryLimit = 10;
-          selector.matchLabels.k8s-app = "kubernetes-dashboard";
-          template = {
-            metadata = {
-              labels = {
-                k8s-addon = "kubernetes-dashboard.addons.k8s.io";
-                k8s-app = "kubernetes-dashboard";
-                version = cfg.version;
-                "kubernetes.io/cluster-service" = "true";
-              };
-              annotations = {
-                "scheduler.alpha.kubernetes.io/critical-pod" = "";
-              };
-            };
-            spec = {
-              priorityClassName = "system-cluster-critical";
-              containers = [{
-                name = "kubernetes-dashboard";
-                image = with cfg.image; "${imageName}:${finalImageTag}";
-                ports = [{
-                  containerPort = 8443;
-                  protocol = "TCP";
-                }];
-                resources = {
-                  limits = {
-                    cpu = "100m";
-                    memory = "300Mi";
-                  };
-                  requests = {
-                    cpu = "100m";
-                    memory = "100Mi";
-                  };
-                };
-                args = ["--auto-generate-certificates"] ++ cfg.extraArgs;
-                volumeMounts = [{
-                  name = "tmp-volume";
-                  mountPath = "/tmp";
-                } {
-                  name = "kubernetes-dashboard-certs";
-                  mountPath = "/certs";
-                }];
-                livenessProbe = {
-                  httpGet = {
-                    scheme = "HTTPS";
-                    path = "/";
-                    port = 8443;
-                  };
-                  initialDelaySeconds = 30;
-                  timeoutSeconds = 30;
-                };
-              }];
-              volumes = [{
-                name = "kubernetes-dashboard-certs";
-                secret = {
-                  secretName = "kubernetes-dashboard-certs";
-                };
-              } {
-                name = "tmp-volume";
-                emptyDir = {};
-              }];
-              serviceAccountName = "kubernetes-dashboard";
-              tolerations = [{
-                key = "node-role.kubernetes.io/master";
-                effect = "NoSchedule";
-              } {
-                key = "CriticalAddonsOnly";
-                operator = "Exists";
-              }];
-            };
-          };
-        };
-      };
-
-      kubernetes-dashboard-svc = {
-        apiVersion = "v1";
-        kind = "Service";
-        metadata = {
-          labels = {
-            k8s-addon = "kubernetes-dashboard.addons.k8s.io";
-            k8s-app = "kubernetes-dashboard";
-            "kubernetes.io/cluster-service" = "true";
-            "kubernetes.io/name" = "KubeDashboard";
-            "addonmanager.kubernetes.io/mode" = "Reconcile";
-          };
-          name = "kubernetes-dashboard";
-          namespace  = "kube-system";
-        };
-        spec = {
-          ports = [{
-            port = 443;
-            targetPort = 8443;
-          }];
-          selector.k8s-app = "kubernetes-dashboard";
-        };
-      };
-
-      kubernetes-dashboard-sa = {
-        apiVersion = "v1";
-        kind = "ServiceAccount";
-        metadata = {
-          labels = {
-            k8s-app = "kubernetes-dashboard";
-            k8s-addon = "kubernetes-dashboard.addons.k8s.io";
-            "addonmanager.kubernetes.io/mode" = "Reconcile";
-          };
-          name = "kubernetes-dashboard";
-          namespace = "kube-system";
-        };
-      };
-      kubernetes-dashboard-sec-certs = {
-        apiVersion = "v1";
-        kind = "Secret";
-        metadata = {
-          labels = {
-            k8s-app = "kubernetes-dashboard";
-            # Allows editing resource and makes sure it is created first.
-            "addonmanager.kubernetes.io/mode" = "EnsureExists";
-          };
-          name = "kubernetes-dashboard-certs";
-          namespace = "kube-system";
-        };
-        type = "Opaque";
-      };
-      kubernetes-dashboard-sec-kholder = {
-        apiVersion = "v1";
-        kind = "Secret";
-        metadata = {
-          labels = {
-            k8s-app = "kubernetes-dashboard";
-            # Allows editing resource and makes sure it is created first.
-            "addonmanager.kubernetes.io/mode" = "EnsureExists";
-          };
-          name = "kubernetes-dashboard-key-holder";
-          namespace = "kube-system";
-        };
-        type = "Opaque";
-      };
-      kubernetes-dashboard-cm = {
-        apiVersion = "v1";
-        kind = "ConfigMap";
-        metadata = {
-          labels = {
-            k8s-app = "kubernetes-dashboard";
-            # Allows editing resource and makes sure it is created first.
-            "addonmanager.kubernetes.io/mode" = "EnsureExists";
-          };
-          name = "kubernetes-dashboard-settings";
-          namespace = "kube-system";
-        };
-      };
-    } // (optionalAttrs cfg.rbac.enable
-      (let
-        subjects = [{
-          kind = "ServiceAccount";
-          name = "kubernetes-dashboard";
-          namespace = "kube-system";
-        }];
-        labels = {
-          k8s-app = "kubernetes-dashboard";
-          k8s-addon = "kubernetes-dashboard.addons.k8s.io";
-          "addonmanager.kubernetes.io/mode" = "Reconcile";
-        };
-      in
-        (if cfg.rbac.clusterAdmin then {
-          kubernetes-dashboard-crb = {
-            apiVersion = "rbac.authorization.k8s.io/v1";
-            kind = "ClusterRoleBinding";
-            metadata = {
-              name = "kubernetes-dashboard";
-              inherit labels;
-            };
-            roleRef = {
-              apiGroup = "rbac.authorization.k8s.io";
-              kind = "ClusterRole";
-              name = "cluster-admin";
-            };
-            inherit subjects;
-          };
-        }
-        else
-        {
-          # Upstream role- and rolebinding as per:
-          # https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/alternative/kubernetes-dashboard.yaml
-          kubernetes-dashboard-role = {
-            apiVersion = "rbac.authorization.k8s.io/v1";
-            kind = "Role";
-            metadata = {
-              name = "kubernetes-dashboard-minimal";
-              namespace = "kube-system";
-              inherit labels;
-            };
-            rules = [
-              # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
-              {
-                apiGroups = [""];
-                resources = ["secrets"];
-                verbs = ["create"];
-              }
-              # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
-              {
-                apiGroups = [""];
-                resources = ["configmaps"];
-                verbs = ["create"];
-              }
-              # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
-              {
-                apiGroups = [""];
-                resources = ["secrets"];
-                resourceNames = ["kubernetes-dashboard-key-holder"];
-                verbs = ["get" "update" "delete"];
-              }
-              # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
-              {
-                apiGroups = [""];
-                resources = ["configmaps"];
-                resourceNames = ["kubernetes-dashboard-settings"];
-                verbs = ["get" "update"];
-              }
-              # Allow Dashboard to get metrics from heapster.
-              {
-                apiGroups = [""];
-                resources = ["services"];
-                resourceNames = ["heapster"];
-                verbs = ["proxy"];
-              }
-              {
-                apiGroups = [""];
-                resources = ["services/proxy"];
-                resourceNames = ["heapster" "http:heapster:" "https:heapster:"];
-                verbs = ["get"];
-              }
-            ];
-          };
-
-          kubernetes-dashboard-rb = {
-            apiVersion = "rbac.authorization.k8s.io/v1";
-            kind = "RoleBinding";
-            metadata = {
-              name = "kubernetes-dashboard-minimal";
-              namespace = "kube-system";
-              inherit labels;
-            };
-            roleRef = {
-              apiGroup = "rbac.authorization.k8s.io";
-              kind = "Role";
-              name = "kubernetes-dashboard-minimal";
-            };
-            inherit subjects;
-          };
-        })
-    ));
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 34943fddd3d1..10f45db7883f 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 
 with lib;
 
@@ -23,6 +23,10 @@ in {
           take 3 (splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange
         ))
       ) + ".254";
+      defaultText = literalDocBook ''
+        The <literal>x.y.z.254</literal> IP of
+        <literal>config.${options.services.kubernetes.apiserver.serviceClusterIpRange}</literal>.
+      '';
       type = types.str;
     };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
index 2c89310beb5a..5b97c571d763 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -1,9 +1,10 @@
-  { config, lib, pkgs, ... }:
+  { config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   top = config.services.kubernetes;
+  otop = options.services.kubernetes;
   cfg = top.apiserver;
 
   isRBACEnabled = elem "RBAC" cfg.authorizationMode;
@@ -84,6 +85,7 @@ in
     clientCaFile = mkOption {
       description = "Kubernetes apiserver CA file for client auth.";
       default = top.caFile;
+      defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
     };
 
@@ -138,6 +140,7 @@ in
       caFile = mkOption {
         description = "Etcd ca file.";
         default = top.caFile;
+        defaultText = literalExpression "config.${otop.caFile}";
         type = types.nullOr types.path;
       };
     };
@@ -157,6 +160,7 @@ in
     featureGates = mkOption {
       description = "List set of feature gates";
       default = top.featureGates;
+      defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
@@ -175,6 +179,7 @@ in
     kubeletClientCaFile = mkOption {
       description = "Path to a cert file for connecting to kubelet.";
       default = top.caFile;
+      defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
     };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 7128b5f70b1a..ed25715fab7d 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   top = config.services.kubernetes;
+  otop = options.services.kubernetes;
   cfg = top.controllerManager;
 in
 {
@@ -30,6 +31,7 @@ in
     clusterCidr = mkOption {
       description = "Kubernetes CIDR Range for Pods in cluster.";
       default = top.clusterCidr;
+      defaultText = literalExpression "config.${otop.clusterCidr}";
       type = str;
     };
 
@@ -44,6 +46,7 @@ in
     featureGates = mkOption {
       description = "List set of feature gates";
       default = top.featureGates;
+      defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
@@ -67,6 +70,7 @@ in
         service account's token secret.
       '';
       default = top.caFile;
+      defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
     };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
index 433adf4d488c..227c69fec36d 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.kubernetes;
+  opt = options.services.kubernetes;
 
   defaultContainerdSettings = {
     version = 2;
@@ -87,6 +88,7 @@ let
       description = "${prefix} certificate authority file used to connect to kube-apiserver.";
       type = types.nullOr types.path;
       default = cfg.caFile;
+      defaultText = literalExpression "config.${opt.caFile}";
     };
 
     certFile = mkOption {
@@ -104,6 +106,7 @@ let
 in {
 
   imports = [
+    (mkRemovedOptionModule [ "services" "kubernetes" "addons" "dashboard" ] "Removed due to it being an outdated version")
     (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "")
   ];
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 3a2a0ed363d6..3e8eac96f6ba 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   top = config.services.kubernetes;
+  otop = options.services.kubernetes;
   cfg = top.kubelet;
 
   cniConfig =
@@ -35,6 +36,7 @@ let
       key = mkOption {
         description = "Key of taint.";
         default = name;
+        defaultText = literalDocBook "Name of this submodule.";
         type = str;
       };
       value = mkOption {
@@ -76,12 +78,14 @@ in
     clusterDomain = mkOption {
       description = "Use alternative domain.";
       default = config.services.kubernetes.addons.dns.clusterDomain;
+      defaultText = literalExpression "config.${options.services.kubernetes.addons.dns.clusterDomain}";
       type = str;
     };
 
     clientCaFile = mkOption {
       description = "Kubernetes apiserver CA file for client authentication.";
       default = top.caFile;
+      defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
     };
 
@@ -148,6 +152,7 @@ in
     featureGates = mkOption {
       description = "List set of feature gates";
       default = top.featureGates;
+      defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
@@ -168,6 +173,7 @@ in
     hostname = mkOption {
       description = "Kubernetes kubelet hostname override.";
       default = config.networking.hostName;
+      defaultText = literalExpression "config.networking.hostName";
       type = str;
     };
 
@@ -258,6 +264,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" ];
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
index faf951d81574..76ab03cd520b 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -98,6 +98,7 @@ in
         the public and private keys respectively.
       '';
       default = "${config.services.cfssl.dataDir}/ca";
+      defaultText = literalExpression ''"''${config.services.cfssl.dataDir}/ca"'';
       type = str;
     };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
index a92043d52597..5f3da034120b 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   top = config.services.kubernetes;
+  otop = options.services.kubernetes;
   cfg = top.proxy;
 in
 {
@@ -31,12 +32,14 @@ in
     featureGates = mkOption {
       description = "List set of feature gates";
       default = top.featureGates;
+      defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
     hostname = mkOption {
       description = "Kubernetes proxy hostname override.";
       default = config.networking.hostName;
+      defaultText = literalExpression "config.networking.hostName";
       type = str;
     };
 
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
index 1b0c22a11426..87263ee72fa4 100644
--- a/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   top = config.services.kubernetes;
+  otop = options.services.kubernetes;
   cfg = top.scheduler;
 in
 {
@@ -27,6 +28,7 @@ in
     featureGates = mkOption {
       description = "List set of feature gates";
       default = top.featureGates;
+      defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
diff --git a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
index 0c96f3231329..7686ff99bfc0 100644
--- a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
 
   cfg = config.services.slurm;
+  opt = options.services.slurm;
   # configuration file can be generated by http://slurm.schedmd.com/configurator.html
 
   defaultUser = "slurm";
@@ -80,6 +81,7 @@ in
         dbdHost = mkOption {
           type = types.str;
           default = config.networking.hostName;
+          defaultText = literalExpression "config.networking.hostName";
           description = ''
             Hostname of the machine where <literal>slurmdbd</literal>
             is running (i.e. name returned by <literal>hostname -s</literal>).
@@ -89,6 +91,7 @@ in
         storageUser = mkOption {
           type = types.str;
           default = cfg.user;
+          defaultText = literalExpression "config.${opt.user}";
           description = ''
             Database user name.
           '';
@@ -153,6 +156,7 @@ in
       controlAddr = mkOption {
         type = types.nullOr types.str;
         default = cfg.controlMachine;
+        defaultText = literalExpression "config.${opt.controlMachine}";
         example = null;
         description = ''
           Name that ControlMachine should be referred to in establishing a
@@ -278,6 +282,10 @@ in
         type = types.path;
         internal = true;
         default = etcSlurm;
+        defaultText = literalDocBook ''
+          Directory created from generated config files and
+          <literal>config.${opt.extraConfigPaths}</literal>.
+        '';
         description = ''
           Path to directory with slurm config files. This option is set by default from the
           Slurm module and is meant to make the Slurm config file available to other modules.
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
index 2dc61c21ac71..aaa159d3cb18 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -1,11 +1,12 @@
 # NixOS module for Buildbot continous integration server.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.buildbot-master;
+  opt = options.services.buildbot-master;
 
   python = cfg.package.pythonModule;
 
@@ -152,6 +153,7 @@ in {
 
       buildbotDir = mkOption {
         default = "${cfg.home}/master";
+        defaultText = literalExpression ''"''${config.${opt.home}}/master"'';
         type = types.path;
         description = "Specifies the Buildbot directory.";
       };
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
index dd4f4a4a74a9..1d7f53bb6559 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -1,11 +1,12 @@
 # NixOS module for Buildbot Worker.
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.buildbot-worker;
+  opt = options.services.buildbot-worker;
 
   python = cfg.package.pythonModule;
 
@@ -77,6 +78,7 @@ in {
 
       buildbotDir = mkOption {
         default = "${cfg.home}/worker";
+        defaultText = literalExpression ''"''${config.${opt.home}}/worker"'';
         type = types.path;
         description = "Specifies the Buildbot directory.";
       };
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix b/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix
index 943c1e4598df..afd85c972b56 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/github-runner.nix
@@ -10,6 +10,8 @@ let
   stateDir = "%S/${systemdDir}";
   # %L: Log directory root (usually /var/log); see systemd.unit(5)
   logsDir = "%L/${systemdDir}";
+  # Name of file stored in service state directory
+  currentConfigTokenFilename = ".current-token";
 in
 {
   options.services.github-runner = {
@@ -58,6 +60,7 @@ in
       '';
       example = "nixos";
       default = config.networking.hostName;
+      defaultText = literalExpression "config.networking.hostName";
     };
 
     runnerGroup = mkOption {
@@ -143,13 +146,11 @@ in
         ExecStart = "${cfg.package}/bin/runsvc.sh";
 
         # Does the following, sequentially:
-        # - Copy the current and the previous `tokenFile` to the $RUNTIME_DIRECTORY
-        #   and make it accessible to the service user to allow for a content
-        #   comparison.
-        # - If the module configuration or the token has changed, clear the state directory.
-        # - Configure the runner.
-        # - Copy the configured `tokenFile` to the $STATE_DIRECTORY and make it
-        #   inaccessible to the service user.
+        # - If the module configuration or the token has changed, purge the state directory,
+        #   and create the current and the new token file with the contents of the configured
+        #   token. While both files have the same content, only the later is accessible by
+        #   the service user.
+        # - Configure the runner using the new token file. When finished, delete it.
         # - Set up the directory structure by creating the necessary symlinks.
         ExecStartPre =
           let
@@ -172,37 +173,20 @@ in
             currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
             runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" ] cfg;
             newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
-            currentConfigTokenFilename = ".current-token";
             newConfigTokenFilename = ".new-token";
             runnerCredFiles = [
               ".credentials"
               ".credentials_rsaparams"
               ".runner"
             ];
-            ownConfigTokens = writeScript "own-config-tokens" ''
-              # Copy current and new token file to runtime dir and make it accessible to the service user
-              cp ${escapeShellArg cfg.tokenFile} "$RUNTIME_DIRECTORY/${newConfigTokenFilename}"
-              chmod 600 "$RUNTIME_DIRECTORY/${newConfigTokenFilename}"
-              chown "$USER" "$RUNTIME_DIRECTORY/${newConfigTokenFilename}"
-
-              if [[ -e "$STATE_DIRECTORY/${currentConfigTokenFilename}" ]]; then
-                cp "$STATE_DIRECTORY/${currentConfigTokenFilename}" "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}"
-                chmod 600 "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}"
-                chown "$USER" "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}"
-              fi
-            '';
-            disownConfigTokens = writeScript "disown-config-tokens" ''
-              # Make the token inaccessible to the runner service user
-              chmod 600 "$STATE_DIRECTORY/${currentConfigTokenFilename}"
-              chown root:root "$STATE_DIRECTORY/${currentConfigTokenFilename}"
-            '';
             unconfigureRunner = writeScript "unconfigure" ''
               differs=
               # Set `differs = 1` if current and new runner config differ or if `currentConfigPath` does not exist
               ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 || differs=1
               # Also trigger a registration if the token content changed
               ${pkgs.diffutils}/bin/diff -q \
-                "$RUNTIME_DIRECTORY"/{${currentConfigTokenFilename},${newConfigTokenFilename}} \
+                "$STATE_DIRECTORY"/${currentConfigTokenFilename} \
+                ${escapeShellArg cfg.tokenFile} \
                 >/dev/null 2>&1 || differs=1
 
               if [[ -n "$differs" ]]; then
@@ -210,13 +194,18 @@ in
                 echo "The old runner will still appear in the GitHub Actions UI." \
                   "You have to remove it manually."
                 find "$STATE_DIRECTORY/" -mindepth 1 -delete
+
+                # Copy the configured token file to the state dir and allow the service user to read the file
+                install --mode=666 ${escapeShellArg cfg.tokenFile} "$STATE_DIRECTORY/${newConfigTokenFilename}"
+                # Also copy current file to allow for a diff on the next start
+                install --mode=600 ${escapeShellArg cfg.tokenFile} "$STATE_DIRECTORY/${currentConfigTokenFilename}"
               fi
             '';
             configureRunner = writeScript "configure" ''
-              empty=$(ls -A "$STATE_DIRECTORY")
-              if [[ -z "$empty" ]]; then
+              if [[ -e "$STATE_DIRECTORY/${newConfigTokenFilename}" ]]; then
                 echo "Configuring GitHub Actions Runner"
-                token=$(< "$RUNTIME_DIRECTORY"/${newConfigTokenFilename})
+
+                token=$(< "$STATE_DIRECTORY"/${newConfigTokenFilename})
                 RUNNER_ROOT="$STATE_DIRECTORY" ${cfg.package}/bin/config.sh \
                   --unattended \
                   --work "$RUNTIME_DIRECTORY" \
@@ -233,8 +222,7 @@ in
                 rm    -rf "$STATE_DIRECTORY/_diag/"
 
                 # Cleanup token from config
-                rm -f "$RUNTIME_DIRECTORY"/${currentConfigTokenFilename}
-                mv    "$RUNTIME_DIRECTORY"/${newConfigTokenFilename} "$STATE_DIRECTORY/${currentConfigTokenFilename}"
+                rm "$STATE_DIRECTORY/${newConfigTokenFilename}"
 
                 # Symlink to new config
                 ln -s '${newConfigPath}' "${currentConfigPath}"
@@ -249,10 +237,8 @@ in
             '';
           in
           map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
-            "+${ownConfigTokens}" # runs as root
-            unconfigureRunner
+            "+${unconfigureRunner}" # runs as root
             configureRunner
-            "+${disownConfigTokens}" # runs as root
             setupRuntimeDir
           ];
 
@@ -265,6 +251,13 @@ in
         StateDirectoryMode = "0700";
         WorkingDirectory = runtimeDir;
 
+        InaccessiblePaths = [
+          # Token file path given in the configuration
+          cfg.tokenFile
+          # Token file in the state directory
+          "${stateDir}/${currentConfigTokenFilename}"
+        ];
+
         # By default, use a dynamically allocated user
         DynamicUser = true;
 
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
index acc3fb12484a..c63998c6736a 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.gocd-agent;
+  opt = options.services.gocd-agent;
 in {
   options = {
     services.gocd-agent = {
@@ -98,6 +99,15 @@ in {
           "-Dcruise.console.publish.interval=10"
           "-Djava.security.egd=file:/dev/./urandom"
         ];
+        defaultText = literalExpression ''
+          [
+            "-Xms''${config.${opt.initialJavaHeapSize}}"
+            "-Xmx''${config.${opt.maxJavaHeapMemory}}"
+            "-Djava.io.tmpdir=/tmp"
+            "-Dcruise.console.publish.interval=10"
+            "-Djava.security.egd=file:/dev/./urandom"
+          ]
+        '';
         description = ''
           Specifies startup command line arguments to pass to Go.CD agent
           java process.
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
index 646bf13ac67a..3540656f9344 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.gocd-server;
+  opt = options.services.gocd-server;
 in {
   options = {
     services.gocd-server = {
@@ -106,6 +107,20 @@ in {
           "-Dcruise.server.port=${toString cfg.port}"
           "-Dcruise.server.ssl.port=${toString cfg.sslPort}"
         ];
+        defaultText = literalExpression ''
+          [
+            "-Xms''${config.${opt.initialJavaHeapSize}}"
+            "-Xmx''${config.${opt.maxJavaHeapMemory}}"
+            "-Dcruise.listen.host=''${config.${opt.listenAddress}}"
+            "-Duser.language=en"
+            "-Djruby.rack.request.size.threshold.bytes=30000000"
+            "-Duser.country=US"
+            "-Dcruise.config.dir=''${config.${opt.workDir}}/conf"
+            "-Dcruise.config.file=''${config.${opt.workDir}}/conf/cruise-config.xml"
+            "-Dcruise.server.port=''${toString config.${opt.port}}"
+            "-Dcruise.server.ssl.port=''${toString config.${opt.sslPort}}"
+          ]
+        '';
 
         description = ''
           Specifies startup command line arguments to pass to Go.CD server
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix b/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
index d53d68bdcf97..80c88714bfc1 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
@@ -1,10 +1,10 @@
 /*
 
-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
@@ -27,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";
@@ -55,6 +65,25 @@ 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.
@@ -66,6 +95,8 @@ let
       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";
@@ -74,24 +105,48 @@ let
       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 = literalExpression ''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;
       };
       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 = literalExpression ''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;
+      };
+      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"'';
       };
     };
   };
@@ -177,7 +232,7 @@ 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;
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
index 06c174e7d376..968bc8f1e54e 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
index d6cde77c0a3f..ccb7cc21734e 100644
--- a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -203,6 +203,7 @@ in
       buildMachinesFiles = mkOption {
         type = types.listOf types.path;
         default = optional (config.nix.buildMachines != []) "/etc/nix/machines";
+        defaultText = literalExpression ''optional (config.nix.buildMachines != []) "/etc/nix/machines"'';
         example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
         description = "List of files containing build machines.";
       };
diff --git a/nixpkgs/nixos/modules/services/databases/clickhouse.nix b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
index f2f4e9d25542..3a161d56107e 100644
--- a/nixpkgs/nixos/modules/services/databases/clickhouse.nix
+++ b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
@@ -13,6 +13,15 @@ with lib;
 
       enable = mkEnableOption "ClickHouse database server";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.clickhouse;
+        defaultText = "pkgs.clickhouse";
+        description = ''
+          ClickHouse package to use.
+        '';
+      };
+
     };
 
   };
@@ -45,21 +54,21 @@ with lib;
         AmbientCapabilities = "CAP_SYS_NICE";
         StateDirectory = "clickhouse";
         LogsDirectory = "clickhouse";
-        ExecStart = "${pkgs.clickhouse}/bin/clickhouse-server --config-file=${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+        ExecStart = "${cfg.package}/bin/clickhouse-server --config-file=${cfg.package}/etc/clickhouse-server/config.xml";
       };
     };
 
     environment.etc = {
       "clickhouse-server/config.xml" = {
-        source = "${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+        source = "${cfg.package}/etc/clickhouse-server/config.xml";
       };
 
       "clickhouse-server/users.xml" = {
-        source = "${pkgs.clickhouse}/etc/clickhouse-server/users.xml";
+        source = "${cfg.package}/etc/clickhouse-server/users.xml";
       };
     };
 
-    environment.systemPackages = [ pkgs.clickhouse ];
+    environment.systemPackages = [ cfg.package ];
 
     # startup requires a `/etc/localtime` which only if exists if `time.timeZone != null`
     time.timeZone = mkDefault "UTC";
diff --git a/nixpkgs/nixos/modules/services/databases/couchdb.nix b/nixpkgs/nixos/modules/services/databases/couchdb.nix
index 16dd64f2373e..266bc82b6967 100644
--- a/nixpkgs/nixos/modules/services/databases/couchdb.nix
+++ b/nixpkgs/nixos/modules/services/databases/couchdb.nix
@@ -43,8 +43,8 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.couchdb;
-        defaultText = literalExpression "pkgs.couchdb";
+        default = pkgs.couchdb3;
+        defaultText = literalExpression "pkgs.couchdb3";
         description = ''
           CouchDB package to use.
         '';
@@ -150,6 +150,14 @@ in {
         '';
       };
 
+      argsFile = mkOption {
+        type = types.path;
+        default = "${cfg.package}/etc/vm.args";
+        description = ''
+          vm.args configuration. Overrides Couchdb's Erlang VM parameters file.
+        '';
+      };
+
       configFile = mkOption {
         type = types.path;
         description = ''
@@ -186,12 +194,14 @@ in {
       '';
 
       environment = {
-        # we are actually specifying 4 configuration files:
+        # we are actually specifying 5 configuration files:
         # 1. the preinstalled default.ini
         # 2. the module configuration
         # 3. the extraConfig from the module options
         # 4. the locally writable config file, which couchdb itself writes to
         ERL_FLAGS= ''-couch_ini ${cfg.package}/etc/default.ini ${configFile} ${pkgs.writeText "couchdb-extra.ini" cfg.extraConfig} ${cfg.configFile}'';
+        # 5. the vm.args file
+        COUCHDB_ARGS_FILE=''${cfg.argsFile}'';
       };
 
       serviceConfig = {
diff --git a/nixpkgs/nixos/modules/services/databases/hbase.nix b/nixpkgs/nixos/modules/services/databases/hbase.nix
index 9132b7ed3569..fe4f05eec643 100644
--- a/nixpkgs/nixos/modules/services/databases/hbase.nix
+++ b/nixpkgs/nixos/modules/services/databases/hbase.nix
@@ -1,22 +1,27 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 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>
-  '';
+  opt = options.services.hbase;
+
+  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 (opt.settings.default // cfg.settings)}
+      </configuration>
+    '';
 
   configDir = pkgs.runCommand "hbase-config-dir" { preferLocalBuild = true; } ''
     mkdir -p $out
@@ -85,6 +90,23 @@ in {
         '';
       };
 
+      settings = mkOption {
+        type = with lib.types; attrsOf (oneOf [ str int bool ]);
+        default = {
+          "hbase.rootdir" = "file://${cfg.dataDir}/hbase";
+          "hbase.zookeeper.property.dataDir" = "${cfg.dataDir}/zookeeper";
+        };
+        defaultText = literalExpression ''
+          {
+            "hbase.rootdir" = "file://''${config.${opt.dataDir}}/hbase";
+            "hbase.zookeeper.property.dataDir" = "''${config.${opt.dataDir}}/zookeeper";
+          }
+        '';
+        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.
+        '';
+      };
+
     };
 
   };
@@ -103,7 +125,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/nixpkgs/nixos/modules/services/databases/influxdb2.nix b/nixpkgs/nixos/modules/services/databases/influxdb2.nix
index 01b9c4934847..340c515bbb43 100644
--- a/nixpkgs/nixos/modules/services/databases/influxdb2.nix
+++ b/nixpkgs/nixos/modules/services/databases/influxdb2.nix
@@ -1,5 +1,7 @@
 { config, lib, pkgs, ... }:
+
 with lib;
+
 let
   format = pkgs.formats.json { };
   cfg = config.services.influxdb2;
@@ -9,15 +11,17 @@ in
   options = {
     services.influxdb2 = {
       enable = mkEnableOption "the influxdb2 server";
+
       package = mkOption {
-        default = pkgs.influxdb2;
+        default = pkgs.influxdb2-server;
         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;
       };
     };
@@ -28,18 +32,20 @@ in
       assertion = !(builtins.hasAttr "bolt-path" cfg.settings) && !(builtins.hasAttr "engine-path" cfg.settings);
       message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
     }];
+
     systemd.services.influxdb2 = {
       description = "InfluxDB is an open-source, distributed, time series database";
       documentation = [ "https://docs.influxdata.com/influxdb/" ];
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       environment = {
-        INFLUXD_CONFIG_PATH = "${configFile}";
+        INFLUXD_CONFIG_PATH = configFile;
       };
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/influxd --bolt-path \${STATE_DIRECTORY}/influxd.bolt --engine-path \${STATE_DIRECTORY}/engine";
         StateDirectory = "influxdb2";
-        DynamicUser = true;
+        User = "influxdb2";
+        Group = "influxdb2";
         CapabilityBoundingSet = "";
         SystemCallFilter = "@system-service";
         LimitNOFILE = 65536;
@@ -47,6 +53,13 @@ in
         Restart = "on-failure";
       };
     };
+
+    users.extraUsers.influxdb2 = {
+      isSystemUser = true;
+      group = "influxdb2";
+    };
+
+    users.extraGroups.influxdb2 = {};
   };
 
   meta.maintainers = with lib.maintainers; [ nickcao ];
diff --git a/nixpkgs/nixos/modules/services/databases/mysql.nix b/nixpkgs/nixos/modules/services/databases/mysql.nix
index a9d9a6d80588..625b31d081c9 100644
--- a/nixpkgs/nixos/modules/services/databases/mysql.nix
+++ b/nixpkgs/nixos/modules/services/databases/mysql.nix
@@ -11,10 +11,8 @@ let
   mysqldOptions =
     "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${cfg.package}";
 
-  settingsFile = pkgs.writeText "my.cnf" (
-    generators.toINI { listsAsDuplicateKeys = true; } cfg.settings +
-    optionalString (cfg.extraOptions != null) "[mysqld]\n${cfg.extraOptions}"
-  );
+  format = pkgs.formats.ini { listsAsDuplicateKeys = true; };
+  configFile = format.generate "my.cnf" cfg.settings;
 
 in
 
@@ -22,6 +20,9 @@ in
   imports = [
     (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd.")
     (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
+    (mkRemovedOptionModule [ "services" "mysql" "extraOptions" ] "Use services.mysql.settings.mysqld instead.")
+    (mkRemovedOptionModule [ "services" "mysql" "bind" ] "Use services.mysql.settings.mysqld.bind-address instead.")
+    (mkRemovedOptionModule [ "services" "mysql" "port" ] "Use services.mysql.settings.mysqld.port instead.")
   ];
 
   ###### interface
@@ -40,41 +41,53 @@ in
         ";
       };
 
-      bind = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "0.0.0.0";
-        description = "Address to bind to. The default is to bind to all addresses.";
-      };
-
-      port = mkOption {
-        type = types.port;
-        default = 3306;
-        description = "Port of MySQL.";
-      };
-
       user = mkOption {
         type = types.str;
         default = "mysql";
-        description = "User account under which MySQL runs.";
+        description = ''
+          User account under which MySQL runs.
+
+          <note><para>
+          If left as the default value this user will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the user exists before the MySQL service starts.
+          </para></note>
+        '';
       };
 
       group = mkOption {
         type = types.str;
         default = "mysql";
-        description = "Group under which MySQL runs.";
+        description = ''
+          Group account under which MySQL runs.
+
+          <note><para>
+          If left as the default value this group will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the user exists before the MySQL service starts.
+          </para></note>
+        '';
       };
 
       dataDir = mkOption {
         type = types.path;
         example = "/var/lib/mysql";
-        description = "Location where MySQL stores its table files.";
+        description = ''
+          The data directory for MySQL.
+
+          <note><para>
+          If left as the default value of <literal>/var/lib/mysql</literal> this directory will automatically be created before the MySQL
+          server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
+          </para></note>
+        '';
       };
 
       configFile = mkOption {
         type = types.path;
-        default = settingsFile;
-        defaultText = literalExpression "settingsFile";
+        default = configFile;
+        defaultText = ''
+          A configuration file automatically generated by NixOS.
+        '';
         description = ''
           Override the configuration file used by MySQL. By default,
           NixOS generates one automatically from <option>services.mysql.settings</option>.
@@ -92,7 +105,7 @@ in
       };
 
       settings = mkOption {
-        type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ]));
+        type = format.type;
         default = {};
         description = ''
           MySQL configuration. Refer to
@@ -125,23 +138,6 @@ in
         '';
       };
 
-      extraOptions = mkOption {
-        type = with types; nullOr lines;
-        default = null;
-        example = ''
-          key_buffer_size = 6G
-          table_cache = 1600
-          log-error = /var/log/mysql_err.log
-        '';
-        description = ''
-          Provide extra options to the MySQL configuration file.
-
-          Please note, that these options are added to the
-          <literal>[mysqld]</literal> section so you don't need to explicitly
-          state it again.
-        '';
-      };
-
       initialDatabases = mkOption {
         type = types.listOf (types.submodule {
           options = {
@@ -287,7 +283,7 @@ in
         };
 
         masterPort = mkOption {
-          type = types.int;
+          type = types.port;
           default = 3306;
           description = "Port number on which the MySQL master server runs.";
         };
@@ -299,9 +295,7 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.mysql.enable {
-
-    warnings = optional (cfg.extraOptions != null) "services.mysql.`extraOptions` is deprecated, please use services.mysql.`settings`.";
+  config = mkIf cfg.enable {
 
     services.mysql.dataDir =
       mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql"
@@ -310,8 +304,7 @@ in
     services.mysql.settings.mysqld = mkMerge [
       {
         datadir = cfg.dataDir;
-        bind-address = mkIf (cfg.bind != null) cfg.bind;
-        port = cfg.port;
+        port = mkDefault 3306;
       }
       (mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") {
         log-bin = "mysql-bin-${toString cfg.replication.serverId}";
@@ -341,156 +334,150 @@ in
 
     environment.etc."my.cnf".source = cfg.configFile;
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
-      "z '${cfg.dataDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
-    ];
-
-    systemd.services.mysql = let
-      hasNotify = isMariaDB;
-    in {
-        description = "MySQL Server";
-
-        after = [ "network.target" ];
-        wantedBy = [ "multi-user.target" ];
-        restartTriggers = [ cfg.configFile ];
-
-        unitConfig.RequiresMountsFor = "${cfg.dataDir}";
-
-        path = [
-          # Needed for the mysql_install_db command in the preStart script
-          # which calls the hostname command.
-          pkgs.nettools
-        ];
-
-        preStart = if isMariaDB then ''
-          if ! test -e ${cfg.dataDir}/mysql; then
-            ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
-            touch ${cfg.dataDir}/mysql_init
-          fi
-        '' else ''
-          if ! test -e ${cfg.dataDir}/mysql; then
-            ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
-            touch ${cfg.dataDir}/mysql_init
-          fi
-        '';
-
-        script = ''
-          # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
-          if test -n "''${_WSREP_START_POSITION}"; then
-            if test -e "${cfg.package}/bin/galera_recovery"; then
-              VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
-            fi
-          fi
-
-          # The last two environment variables are used for starting Galera clusters
-          exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
-        '';
-
-        postStart = let
-          # The super user account to use on *first* run of MySQL server
-          superUser = if isMariaDB then cfg.user else "root";
-        in ''
-          ${optionalString (!hasNotify) ''
-            # Wait until the MySQL server is available for use
-            count=0
-            while [ ! -e /run/mysqld/mysqld.sock ]
-            do
-                if [ $count -eq 30 ]
-                then
-                    echo "Tried 30 times, giving up..."
-                    exit 1
-                fi
-
-                echo "MySQL daemon not yet started. Waiting for 1 second..."
-                count=$((count++))
-                sleep 1
-            done
-          ''}
-
-          if [ -f ${cfg.dataDir}/mysql_init ]
-          then
-              # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
-              # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
-              ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
-                echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
-              ) | ${cfg.package}/bin/mysql -u ${superUser} -N
-
-              ${concatMapStrings (database: ''
-                # Create initial databases
-                if ! test -e "${cfg.dataDir}/${database.name}"; then
-                    echo "Creating initial database: ${database.name}"
-                    ( echo 'create database `${database.name}`;'
-
-                      ${optionalString (database.schema != null) ''
-                      echo 'use `${database.name}`;'
-
-                      # TODO: this silently falls through if database.schema does not exist,
-                      # we should catch this somehow and exit, but can't do it here because we're in a subshell.
-                      if [ -f "${database.schema}" ]
-                      then
-                          cat ${database.schema}
-                      elif [ -d "${database.schema}" ]
-                      then
-                          cat ${database.schema}/mysql-databases/*.sql
-                      fi
-                      ''}
-                    ) | ${cfg.package}/bin/mysql -u ${superUser} -N
-                fi
-              '') cfg.initialDatabases}
-
-              ${optionalString (cfg.replication.role == "master")
-                ''
-                  # Set up the replication master
-
-                  ( echo "use mysql;"
-                    echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
-                    echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
-                    echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
-                  ) | ${cfg.package}/bin/mysql -u ${superUser} -N
-                ''}
-
-              ${optionalString (cfg.replication.role == "slave")
-                ''
-                  # Set up the replication slave
-
-                  ( echo "stop slave;"
-                    echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
-                    echo "start slave;"
-                  ) | ${cfg.package}/bin/mysql -u ${superUser} -N
-                ''}
-
-              ${optionalString (cfg.initialScript != null)
-                ''
-                  # Execute initial script
-                  # using toString to avoid copying the file to nix store if given as path instead of string,
-                  # as it might contain credentials
-                  cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
-                ''}
-
-              rm ${cfg.dataDir}/mysql_init
+    systemd.services.mysql = {
+      description = "MySQL Server";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ cfg.configFile ];
+
+      unitConfig.RequiresMountsFor = cfg.dataDir;
+
+      path = [
+        # Needed for the mysql_install_db command in the preStart script
+        # which calls the hostname command.
+        pkgs.nettools
+      ];
+
+      preStart = if isMariaDB then ''
+        if ! test -e ${cfg.dataDir}/mysql; then
+          ${cfg.package}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
+          touch ${cfg.dataDir}/mysql_init
+        fi
+      '' else ''
+        if ! test -e ${cfg.dataDir}/mysql; then
+          ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
+          touch ${cfg.dataDir}/mysql_init
+        fi
+      '';
+
+      script = ''
+        # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery
+        if test -n "''${_WSREP_START_POSITION}"; then
+          if test -e "${cfg.package}/bin/galera_recovery"; then
+            VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1
           fi
+        fi
+
+        # The last two environment variables are used for starting Galera clusters
+        exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION
+      '';
+
+      postStart = let
+        # The super user account to use on *first* run of MySQL server
+        superUser = if isMariaDB then cfg.user else "root";
+      in ''
+        ${optionalString (!isMariaDB) ''
+          # Wait until the MySQL server is available for use
+          count=0
+          while [ ! -e /run/mysqld/mysqld.sock ]
+          do
+              if [ $count -eq 30 ]
+              then
+                  echo "Tried 30 times, giving up..."
+                  exit 1
+              fi
+
+              echo "MySQL daemon not yet started. Waiting for 1 second..."
+              count=$((count++))
+              sleep 1
+          done
+        ''}
+
+        if [ -f ${cfg.dataDir}/mysql_init ]
+        then
+            # While MariaDB comes with a 'mysql' super user account since 10.4.x, MySQL does not
+            # Since we don't want to run this service as 'root' we need to ensure the account exists on first run
+            ( echo "CREATE USER IF NOT EXISTS '${cfg.user}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+              echo "GRANT ALL PRIVILEGES ON *.* TO '${cfg.user}'@'localhost' WITH GRANT OPTION;"
+            ) | ${cfg.package}/bin/mysql -u ${superUser} -N
 
-          ${optionalString (cfg.ensureDatabases != []) ''
-            (
             ${concatMapStrings (database: ''
-              echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
-            '') cfg.ensureDatabases}
+              # Create initial databases
+              if ! test -e "${cfg.dataDir}/${database.name}"; then
+                  echo "Creating initial database: ${database.name}"
+                  ( echo 'create database `${database.name}`;'
+
+                    ${optionalString (database.schema != null) ''
+                    echo 'use `${database.name}`;'
+
+                    # TODO: this silently falls through if database.schema does not exist,
+                    # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+                    if [ -f "${database.schema}" ]
+                    then
+                        cat ${database.schema}
+                    elif [ -d "${database.schema}" ]
+                    then
+                        cat ${database.schema}/mysql-databases/*.sql
+                    fi
+                    ''}
+                  ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+              fi
+            '') cfg.initialDatabases}
+
+            ${optionalString (cfg.replication.role == "master")
+              ''
+                # Set up the replication master
+
+                ( echo "use mysql;"
+                  echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+                  echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+                  echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+                ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+              ''}
+
+            ${optionalString (cfg.replication.role == "slave")
+              ''
+                # Set up the replication slave
+
+                ( echo "stop slave;"
+                  echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+                  echo "start slave;"
+                ) | ${cfg.package}/bin/mysql -u ${superUser} -N
+              ''}
+
+            ${optionalString (cfg.initialScript != null)
+              ''
+                # Execute initial script
+                # using toString to avoid copying the file to nix store if given as path instead of string,
+                # as it might contain credentials
+                cat ${toString cfg.initialScript} | ${cfg.package}/bin/mysql -u ${superUser} -N
+              ''}
+
+            rm ${cfg.dataDir}/mysql_init
+        fi
+
+        ${optionalString (cfg.ensureDatabases != []) ''
+          (
+          ${concatMapStrings (database: ''
+            echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+          '') cfg.ensureDatabases}
+          ) | ${cfg.package}/bin/mysql -N
+        ''}
+
+        ${concatMapStrings (user:
+          ''
+            ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+              ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+              '') user.ensurePermissions)}
             ) | ${cfg.package}/bin/mysql -N
-          ''}
-
-          ${concatMapStrings (user:
-            ''
-              ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
-                ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
-                  echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
-                '') user.ensurePermissions)}
-              ) | ${cfg.package}/bin/mysql -N
-            '') cfg.ensureUsers}
-        '';
+          '') cfg.ensureUsers}
+      '';
 
-        serviceConfig = {
-          Type = if hasNotify then "notify" else "simple";
+      serviceConfig = mkMerge [
+        {
+          Type = if isMariaDB then "notify" else "simple";
           Restart = "on-abort";
           RestartSec = "5s";
 
@@ -523,9 +510,12 @@ in
           PrivateMounts = true;
           # System Call Filtering
           SystemCallArchitectures = "native";
-        };
-      };
-
+        }
+        (mkIf (cfg.dataDir == "/var/lib/mysql") {
+          StateDirectory = "mysql";
+          StateDirectoryMode = "0700";
+        })
+      ];
+    };
   };
-
 }
diff --git a/nixpkgs/nixos/modules/services/databases/neo4j.nix b/nixpkgs/nixos/modules/services/databases/neo4j.nix
index f37e5ad16939..8816f3b2e4b6 100644
--- a/nixpkgs/nixos/modules/services/databases/neo4j.nix
+++ b/nixpkgs/nixos/modules/services/databases/neo4j.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.neo4j;
+  opt = options.services.neo4j;
   certDirOpt = options.services.neo4j.directories.certificates;
   isDefaultPathOption = opt: isOption opt && opt.type == types.path && opt.highestPrio >= 1500;
 
@@ -256,6 +257,7 @@ in {
       certificates = mkOption {
         type = types.path;
         default = "${cfg.directories.home}/certificates";
+        defaultText = literalExpression ''"''${config.${opt.directories.home}}/certificates"'';
         description = ''
           Directory for storing certificates to be used by Neo4j for
           TLS connections.
@@ -280,6 +282,7 @@ in {
       data = mkOption {
         type = types.path;
         default = "${cfg.directories.home}/data";
+        defaultText = literalExpression ''"''${config.${opt.directories.home}}/data"'';
         description = ''
           Path of the data directory. You must not configure more than one
           Neo4j installation to use the same data directory.
@@ -305,6 +308,7 @@ in {
       imports = mkOption {
         type = types.path;
         default = "${cfg.directories.home}/import";
+        defaultText = literalExpression ''"''${config.${opt.directories.home}}/import"'';
         description = ''
           The root directory for file URLs used with the Cypher
           <literal>LOAD CSV</literal> clause. Only meaningful when
@@ -321,6 +325,7 @@ in {
       plugins = mkOption {
         type = types.path;
         default = "${cfg.directories.home}/plugins";
+        defaultText = literalExpression ''"''${config.${opt.directories.home}}/plugins"'';
         description = ''
           Path of the database plugin directory. Compiled Java JAR files that
           contain database procedures will be loaded if they are placed in
@@ -432,6 +437,7 @@ in {
           baseDirectory = mkOption {
             type = types.path;
             default = "${cfg.directories.certificates}/${name}";
+            defaultText = literalExpression ''"''${config.${opt.directories.certificates}}/''${name}"'';
             description = ''
               The mandatory base directory for cryptographic objects of this
               policy. This path is only automatically generated when this
@@ -493,6 +499,7 @@ in {
           revokedDir = mkOption {
             type = types.path;
             default = "${config.baseDirectory}/revoked";
+            defaultText = literalExpression ''"''${config.${options.baseDirectory}}/revoked"'';
             description = ''
               Path to directory of CRLs (Certificate Revocation Lists) in
               PEM format. Must be an absolute path. The existence of this
@@ -528,6 +535,7 @@ in {
           trustedDir = mkOption {
             type = types.path;
             default = "${config.baseDirectory}/trusted";
+            defaultText = literalExpression ''"''${config.${options.baseDirectory}}/trusted"'';
             description = ''
               Path to directory of X.509 certificates in PEM format for
               trusted parties. Must be an absolute path. The existence of this
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.nix b/nixpkgs/nixos/modules/services/databases/postgresql.nix
index d49cb4c51a72..2919022496a3 100644
--- a/nixpkgs/nixos/modules/services/databases/postgresql.nix
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.nix
@@ -289,14 +289,16 @@ in
         port = cfg.port;
       };
 
-    services.postgresql.package =
+    services.postgresql.package = let
+        mkThrow = ver: throw "postgresql_${ver} was removed, please upgrade your postgresql version.";
+    in
       # Note: when changing the default, make it conditional on
       # ‘system.stateVersion’ to maintain compatibility with existing
       # systems!
       mkDefault (if versionAtLeast config.system.stateVersion "21.11" then pkgs.postgresql_13
             else if versionAtLeast config.system.stateVersion "20.03" then pkgs.postgresql_11
-            else if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6
-            else throw "postgresql_9_5 was removed, please upgrade your postgresql version.");
+            else if versionAtLeast config.system.stateVersion "17.09" then mkThrow "9_6"
+            else mkThrow "9_5");
 
     services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}";
 
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.xml b/nixpkgs/nixos/modules/services/databases/postgresql.xml
index 07af4c937f03..0ca9f3faed21 100644
--- a/nixpkgs/nixos/modules/services/databases/postgresql.xml
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.xml
@@ -52,37 +52,51 @@ Type "help" for help.
  <section xml:id="module-services-postgres-upgrading">
   <title>Upgrading</title>
 
+  <note>
+   <para>
+    The steps below demonstrate how to upgrade from an older version to <package>pkgs.postgresql_13</package>.
+    These instructions are also applicable to other versions.
+   </para>
+  </note>
   <para>
-   Major PostgreSQL upgrade requires PostgreSQL downtime and a few imperative steps to be called. To simplify this process, use the following NixOS module:
+   Major PostgreSQL upgrades require a downtime and a few imperative steps to be called. This is the case because
+   each major version has some internal changes in the databases' state during major releases. Because of that,
+   NixOS places the state into <filename>/var/lib/postgresql/&lt;version&gt;</filename> where each <literal>version</literal>
+   can be obtained like this:
 <programlisting>
-  containers.temp-pg.config.services.postgresql = {
-    enable = true;
-    package = pkgs.postgresql_12;
-    ## set a custom new dataDir
-    # dataDir = "/some/data/dir";
-  };
-  environment.systemPackages =
-    let newpg = config.containers.temp-pg.config.services.postgresql;
-    in [
-      (pkgs.writeScriptBin "upgrade-pg-cluster" ''
-        set -x
-        export OLDDATA="${config.services.postgresql.dataDir}"
-        export NEWDATA="${newpg.dataDir}"
-        export OLDBIN="${config.services.postgresql.package}/bin"
-        export NEWBIN="${newpg.package}/bin"
-
-        install -d -m 0700 -o postgres -g postgres "$NEWDATA"
-        cd "$NEWDATA"
-        sudo -u postgres $NEWBIN/initdb -D "$NEWDATA"
-
-        systemctl stop postgresql    # old one
-
-        sudo -u postgres $NEWBIN/pg_upgrade \
-          --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \
-          --old-bindir $OLDBIN --new-bindir $NEWBIN \
-          "$@"
-      '')
-    ];
+<prompt>$ </prompt>nix-instantiate --eval -A postgresql_13.psqlSchema
+"13"
+</programlisting>
+   For an upgrade, a script like this can be used to simplify the process:
+<programlisting>
+{ config, pkgs, ... }:
+{
+  <xref linkend="opt-environment.systemPackages" /> = [
+    (pkgs.writeScriptBin "upgrade-pg-cluster" ''
+      set -eux
+      # XXX it's perhaps advisable to stop all services that depend on postgresql
+      systemctl stop postgresql
+
+      # XXX replace `&lt;new version&gt;` with the psqlSchema here
+      export NEWDATA="/var/lib/postgresql/&lt;new version&gt;"
+
+      # XXX specify the postgresql package you'd like to upgrade to
+      export NEWBIN="${pkgs.postgresql_13}/bin"
+
+      export OLDDATA="${config.<xref linkend="opt-services.postgresql.dataDir"/>}"
+      export OLDBIN="${config.<xref linkend="opt-services.postgresql.package"/>}/bin"
+
+      install -d -m 0700 -o postgres -g postgres "$NEWDATA"
+      cd "$NEWDATA"
+      sudo -u postgres $NEWBIN/initdb -D "$NEWDATA"
+
+      sudo -u postgres $NEWBIN/pg_upgrade \
+        --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \
+        --old-bindir $OLDBIN --new-bindir $NEWBIN \
+        "$@"
+    '')
+  ];
+}
 </programlisting>
   </para>
 
@@ -103,17 +117,25 @@ Type "help" for help.
    </listitem>
    <listitem>
     <para>
-     Run <literal>upgrade-pg-cluster</literal>. It will stop old postgresql, initialize new one and migrate old one to new one. You may supply arguments like <literal>--jobs 4</literal> and <literal>--link</literal> to speedup migration process. See <link xlink:href="https://www.postgresql.org/docs/current/pgupgrade.html" /> for details.
+     Run <literal>upgrade-pg-cluster</literal>. It will stop old postgresql, initialize a new one and migrate the old one to the new one. You may supply arguments like <literal>--jobs 4</literal> and <literal>--link</literal> to speedup migration process. See <link xlink:href="https://www.postgresql.org/docs/current/pgupgrade.html" /> for details.
     </para>
    </listitem>
    <listitem>
     <para>
-     Change postgresql package in NixOS configuration to the one you were upgrading to, and change <literal>dataDir</literal> to the one you have migrated to. Rebuild NixOS. This should start new postgres using upgraded data directory.
+     Change postgresql package in NixOS configuration to the one you were upgrading to via <xref linkend="opt-services.postgresql.package" />. Rebuild NixOS. This should start new postgres using upgraded data directory and all services you stopped during the upgrade.
     </para>
    </listitem>
    <listitem>
     <para>
-     After upgrade you may want to <literal>ANALYZE</literal> new db.
+     After the upgrade it's advisable to analyze the new cluster (as <literal>su -l postgres</literal> in the
+     <xref linkend="opt-services.postgresql.dataDir" />, in this example <filename>/var/lib/postgresql/13</filename>):
+<programlisting>
+<prompt>$ </prompt>./analyze_new_cluster.sh
+</programlisting>
+     <warning><para>The next step removes the old state-directory!</para></warning>
+<programlisting>
+<prompt>$ </prompt>./delete_old_cluster.sh
+</programlisting>
     </para>
    </listitem>
   </orderedlist>
diff --git a/nixpkgs/nixos/modules/services/databases/redis.nix b/nixpkgs/nixos/modules/services/databases/redis.nix
index 578d9d9ec8d7..c5513635392c 100644
--- a/nixpkgs/nixos/modules/services/databases/redis.nix
+++ b/nixpkgs/nixos/modules/services/databases/redis.nix
@@ -5,17 +5,18 @@ with lib;
 let
   cfg = config.services.redis;
 
-  ulimitNofile = cfg.maxclients + 32;
-
   mkValueString = value:
     if value == true then "yes"
     else if value == false then "no"
     else generators.mkValueStringDefault { } value;
 
-  redisConfig = pkgs.writeText "redis.conf" (generators.toKeyValue {
+  redisConfig = settings: pkgs.writeText "redis.conf" (generators.toKeyValue {
     listsAsDuplicateKeys = true;
     mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " ";
-  } cfg.settings);
+  } settings);
+
+  redisName = name: "redis" + optionalString (name != "") ("-"+name);
+  enabledServers = filterAttrs (name: conf: conf.enable) config.services.redis.servers;
 
 in {
   imports = [
@@ -24,7 +25,28 @@ in {
     (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
     (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
     (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
-    (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.settings instead.")
+    (mkRemovedOptionModule [ "services" "redis" "extraConfig" ] "Use services.redis.servers.*.settings instead.")
+    (mkRenamedOptionModule [ "services" "redis" "enable"] [ "services" "redis" "servers" "" "enable" ])
+    (mkRenamedOptionModule [ "services" "redis" "port"] [ "services" "redis" "servers" "" "port" ])
+    (mkRenamedOptionModule [ "services" "redis" "openFirewall"] [ "services" "redis" "servers" "" "openFirewall" ])
+    (mkRenamedOptionModule [ "services" "redis" "bind"] [ "services" "redis" "servers" "" "bind" ])
+    (mkRenamedOptionModule [ "services" "redis" "unixSocket"] [ "services" "redis" "servers" "" "unixSocket" ])
+    (mkRenamedOptionModule [ "services" "redis" "unixSocketPerm"] [ "services" "redis" "servers" "" "unixSocketPerm" ])
+    (mkRenamedOptionModule [ "services" "redis" "logLevel"] [ "services" "redis" "servers" "" "logLevel" ])
+    (mkRenamedOptionModule [ "services" "redis" "logfile"] [ "services" "redis" "servers" "" "logfile" ])
+    (mkRenamedOptionModule [ "services" "redis" "syslog"] [ "services" "redis" "servers" "" "syslog" ])
+    (mkRenamedOptionModule [ "services" "redis" "databases"] [ "services" "redis" "servers" "" "databases" ])
+    (mkRenamedOptionModule [ "services" "redis" "maxclients"] [ "services" "redis" "servers" "" "maxclients" ])
+    (mkRenamedOptionModule [ "services" "redis" "save"] [ "services" "redis" "servers" "" "save" ])
+    (mkRenamedOptionModule [ "services" "redis" "slaveOf"] [ "services" "redis" "servers" "" "slaveOf" ])
+    (mkRenamedOptionModule [ "services" "redis" "masterAuth"] [ "services" "redis" "servers" "" "masterAuth" ])
+    (mkRenamedOptionModule [ "services" "redis" "requirePass"] [ "services" "redis" "servers" "" "requirePass" ])
+    (mkRenamedOptionModule [ "services" "redis" "requirePassFile"] [ "services" "redis" "servers" "" "requirePassFile" ])
+    (mkRenamedOptionModule [ "services" "redis" "appendOnly"] [ "services" "redis" "servers" "" "appendOnly" ])
+    (mkRenamedOptionModule [ "services" "redis" "appendFsync"] [ "services" "redis" "servers" "" "appendFsync" ])
+    (mkRenamedOptionModule [ "services" "redis" "slowLogLogSlowerThan"] [ "services" "redis" "servers" "" "slowLogLogSlowerThan" ])
+    (mkRenamedOptionModule [ "services" "redis" "slowLogMaxLen"] [ "services" "redis" "servers" "" "slowLogMaxLen" ])
+    (mkRenamedOptionModule [ "services" "redis" "settings"] [ "services" "redis" "servers" "" "settings" ])
   ];
 
   ###### interface
@@ -32,18 +54,6 @@ in {
   options = {
 
     services.redis = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable the Redis server. Note that the NixOS module for
-          Redis disables kernel support for Transparent Huge Pages (THP),
-          because this features causes major performance problems for Redis,
-          e.g. (https://redis.io/topics/latency).
-        '';
-      };
-
       package = mkOption {
         type = types.package;
         default = pkgs.redis;
@@ -51,176 +61,226 @@ in {
         description = "Which Redis derivation to use.";
       };
 
-      port = mkOption {
-        type = types.port;
-        default = 6379;
-        description = "The port for Redis to listen to.";
-      };
-
-      vmOverCommit = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq)
-        '';
-      };
+      vmOverCommit = mkEnableOption ''
+        setting of vm.overcommit_memory to 1
+        (Suggested for Background Saving: http://redis.io/topics/faq)
+      '';
 
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to open ports in the firewall for the server.
-        '';
-      };
+      servers = mkOption {
+        type = with types; attrsOf (submodule ({config, name, ...}@args: {
+          options = {
+            enable = mkEnableOption ''
+              Redis server.
+
+              Note that the NixOS module for Redis disables kernel support
+              for Transparent Huge Pages (THP),
+              because this features causes major performance problems for Redis,
+              e.g. (https://redis.io/topics/latency).
+            '';
+
+            user = mkOption {
+              type = types.str;
+              default = redisName name;
+              defaultText = "\"redis\" or \"redis-\${name}\" if name != \"\"";
+              description = "The username and groupname for redis-server.";
+            };
 
-      bind = mkOption {
-        type = with types; nullOr str;
-        default = "127.0.0.1";
-        description = ''
-          The IP interface to bind to.
-          <literal>null</literal> means "all interfaces".
-        '';
-        example = "192.0.2.1";
-      };
+            port = mkOption {
+              type = types.port;
+              default = 6379;
+              description = "The port for Redis to listen to.";
+            };
 
-      unixSocket = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        description = "The path to the socket to bind to.";
-        example = "/run/redis/redis.sock";
-      };
+            openFirewall = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether to open ports in the firewall for the server.
+              '';
+            };
 
-      unixSocketPerm = mkOption {
-        type = types.int;
-        default = 750;
-        description = "Change permissions for the socket";
-        example = 700;
-      };
+            bind = mkOption {
+              type = with types; nullOr str;
+              default = if name == "" then "127.0.0.1" else null;
+              defaultText = "127.0.0.1 or null if name != \"\"";
+              description = ''
+                The IP interface to bind to.
+                <literal>null</literal> means "all interfaces".
+              '';
+              example = "192.0.2.1";
+            };
 
-      logLevel = mkOption {
-        type = types.str;
-        default = "notice"; # debug, verbose, notice, warning
-        example = "debug";
-        description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
-      };
+            unixSocket = mkOption {
+              type = with types; nullOr path;
+              default = "/run/${redisName name}/redis.sock";
+              defaultText = "\"/run/redis/redis.sock\" or \"/run/redis-\${name}/redis.sock\" if name != \"\"";
+              description = "The path to the socket to bind to.";
+            };
 
-      logfile = mkOption {
-        type = types.str;
-        default = "/dev/null";
-        description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
-        example = "/var/log/redis.log";
-      };
+            unixSocketPerm = mkOption {
+              type = types.int;
+              default = 660;
+              description = "Change permissions for the socket";
+              example = 600;
+            };
 
-      syslog = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Enable logging to the system logger.";
-      };
+            logLevel = mkOption {
+              type = types.str;
+              default = "notice"; # debug, verbose, notice, warning
+              example = "debug";
+              description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
+            };
 
-      databases = mkOption {
-        type = types.int;
-        default = 16;
-        description = "Set the number of databases.";
-      };
+            logfile = mkOption {
+              type = types.str;
+              default = "/dev/null";
+              description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
+              example = "/var/log/redis.log";
+            };
 
-      maxclients = mkOption {
-        type = types.int;
-        default = 10000;
-        description = "Set the max number of connected clients at the same time.";
-      };
+            syslog = mkOption {
+              type = types.bool;
+              default = true;
+              description = "Enable logging to the system logger.";
+            };
 
-      save = mkOption {
-        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.";
-      };
+            databases = mkOption {
+              type = types.int;
+              default = 16;
+              description = "Set the number of databases.";
+            };
 
-      slaveOf = mkOption {
-        type = with types; nullOr (submodule ({ ... }: {
-          options = {
-            ip = mkOption {
-              type = str;
-              description = "IP of the Redis master";
-              example = "192.168.1.100";
+            maxclients = mkOption {
+              type = types.int;
+              default = 10000;
+              description = "Set the max number of connected clients at the same time.";
             };
 
-            port = mkOption {
-              type = port;
-              description = "port of the Redis master";
-              default = 6379;
+            save = mkOption {
+              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.";
             };
-          };
-        }));
 
-        default = null;
-        description = "IP and port to which this redis instance acts as a slave.";
-        example = { ip = "192.168.1.100"; port = 6379; };
-      };
+            slaveOf = mkOption {
+              type = with types; nullOr (submodule ({ ... }: {
+                options = {
+                  ip = mkOption {
+                    type = str;
+                    description = "IP of the Redis master";
+                    example = "192.168.1.100";
+                  };
+
+                  port = mkOption {
+                    type = port;
+                    description = "port of the Redis master";
+                    default = 6379;
+                  };
+                };
+              }));
+
+              default = null;
+              description = "IP and port to which this redis instance acts as a slave.";
+              example = { ip = "192.168.1.100"; port = 6379; };
+            };
 
-      masterAuth = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = ''If the master is password protected (using the requirePass configuration)
-        it is possible to tell the slave to authenticate before starting the replication synchronization
-        process, otherwise the master will refuse the slave request.
-        (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
-      };
+            masterAuth = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''If the master is password protected (using the requirePass configuration)
+              it is possible to tell the slave to authenticate before starting the replication synchronization
+              process, otherwise the master will refuse the slave request.
+              (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
+            };
 
-      requirePass = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = ''
-          Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
-          Use requirePassFile to store it outside of the nix store in a dedicated file.
-        '';
-        example = "letmein!";
-      };
+            requirePass = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
+                Use requirePassFile to store it outside of the nix store in a dedicated file.
+              '';
+              example = "letmein!";
+            };
 
-      requirePassFile = mkOption {
-        type = with types; nullOr path;
-        default = null;
-        description = "File with password for the database.";
-        example = "/run/keys/redis-password";
-      };
+            requirePassFile = mkOption {
+              type = with types; nullOr path;
+              default = null;
+              description = "File with password for the database.";
+              example = "/run/keys/redis-password";
+            };
 
-      appendOnly = mkOption {
-        type = types.bool;
-        default = false;
-        description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
-      };
+            appendOnly = mkOption {
+              type = types.bool;
+              default = false;
+              description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
+            };
 
-      appendFsync = mkOption {
-        type = types.str;
-        default = "everysec"; # no, always, everysec
-        description = "How often to fsync the append-only log, options: no, always, everysec.";
-      };
+            appendFsync = mkOption {
+              type = types.str;
+              default = "everysec"; # no, always, everysec
+              description = "How often to fsync the append-only log, options: no, always, everysec.";
+            };
 
-      slowLogLogSlowerThan = mkOption {
-        type = types.int;
-        default = 10000;
-        description = "Log queries whose execution take longer than X in milliseconds.";
-        example = 1000;
-      };
+            slowLogLogSlowerThan = mkOption {
+              type = types.int;
+              default = 10000;
+              description = "Log queries whose execution take longer than X in milliseconds.";
+              example = 1000;
+            };
 
-      slowLogMaxLen = mkOption {
-        type = types.int;
-        default = 128;
-        description = "Maximum number of items to keep in slow log.";
-      };
+            slowLogMaxLen = mkOption {
+              type = types.int;
+              default = 128;
+              description = "Maximum number of items to keep in slow log.";
+            };
 
-      settings = mkOption {
-        type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+            settings = mkOption {
+              # TODO: this should be converted to freeformType
+              type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+              default = {};
+              description = ''
+                Redis configuration. Refer to
+                <link xlink:href="https://redis.io/topics/config"/>
+                for details on supported values.
+              '';
+              example = literalExpression ''
+                {
+                  loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
+                }
+              '';
+            };
+          };
+          config.settings = mkMerge [
+            {
+              port = if config.bind == null then 0 else config.port;
+              daemonize = false;
+              supervised = "systemd";
+              loglevel = config.logLevel;
+              logfile = config.logfile;
+              syslog-enabled = config.syslog;
+              databases = config.databases;
+              maxclients = config.maxclients;
+              save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save;
+              dbfilename = "dump.rdb";
+              dir = "/var/lib/${redisName name}";
+              appendOnly = config.appendOnly;
+              appendfsync = config.appendFsync;
+              slowlog-log-slower-than = config.slowLogLogSlowerThan;
+              slowlog-max-len = config.slowLogMaxLen;
+            }
+            (mkIf (config.bind != null) { bind = config.bind; })
+            (mkIf (config.unixSocket != null) {
+              unixsocket = config.unixSocket;
+              unixsocketperm = toString config.unixSocketPerm;
+            })
+            (mkIf (config.slaveOf != null) { slaveof = "${config.slaveOf.ip} ${toString config.slaveOf.port}"; })
+            (mkIf (config.masterAuth != null) { masterauth = config.masterAuth; })
+            (mkIf (config.requirePass != null) { requirepass = config.requirePass; })
+          ];
+        }));
+        description = "Configuration of multiple <literal>redis-server</literal> instances.";
         default = {};
-        description = ''
-          Redis configuration. Refer to
-          <link xlink:href="https://redis.io/topics/config"/>
-          for details on supported values.
-        '';
-        example = literalExpression ''
-          {
-            loadmodule = [ "/path/to/my_module.so" "/path/to/other_module.so" ];
-          }
-        '';
       };
     };
 
@@ -229,78 +289,61 @@ in {
 
   ###### implementation
 
-  config = mkIf config.services.redis.enable {
-    assertions = [{
-      assertion = cfg.requirePass != null -> cfg.requirePassFile == null;
-      message = "You can only set one services.redis.requirePass or services.redis.requirePassFile";
-    }];
-    boot.kernel.sysctl = (mkMerge [
+  config = mkIf (enabledServers != {}) {
+
+    assertions = attrValues (mapAttrs (name: conf: {
+      assertion = conf.requirePass != null -> conf.requirePassFile == null;
+      message = ''
+        You can only set one services.redis.servers.${name}.requirePass
+        or services.redis.servers.${name}.requirePassFile
+      '';
+    }) enabledServers);
+
+    boot.kernel.sysctl = mkMerge [
       { "vm.nr_hugepages" = "0"; }
       ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } )
-    ]);
+    ];
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.port ];
-    };
-
-    users.users.redis = {
-      description = "Redis database user";
-      group = "redis";
-      isSystemUser = true;
-    };
-    users.groups.redis = {};
+    networking.firewall.allowedTCPPorts = concatMap (conf:
+      optional conf.openFirewall conf.port
+    ) (attrValues enabledServers);
 
     environment.systemPackages = [ cfg.package ];
 
-    services.redis.settings = mkMerge [
-      {
-        port = cfg.port;
-        daemonize = false;
-        supervised = "systemd";
-        loglevel = cfg.logLevel;
-        logfile = cfg.logfile;
-        syslog-enabled = cfg.syslog;
-        databases = cfg.databases;
-        maxclients = cfg.maxclients;
-        save = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") cfg.save;
-        dbfilename = "dump.rdb";
-        dir = "/var/lib/redis";
-        appendOnly = cfg.appendOnly;
-        appendfsync = cfg.appendFsync;
-        slowlog-log-slower-than = cfg.slowLogLogSlowerThan;
-        slowlog-max-len = cfg.slowLogMaxLen;
-      }
-      (mkIf (cfg.bind != null) { bind = cfg.bind; })
-      (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; })
-      (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}"; })
-      (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; })
-      (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; })
-    ];
+    users.users = mapAttrs' (name: conf: nameValuePair (redisName name) {
+      description = "System user for the redis-server instance ${name}";
+      isSystemUser = true;
+      group = redisName name;
+    }) enabledServers;
+    users.groups = mapAttrs' (name: conf: nameValuePair (redisName name) {
+    }) enabledServers;
 
-    systemd.services.redis = {
-      description = "Redis Server";
+    systemd.services = mapAttrs' (name: conf: nameValuePair (redisName name) {
+      description = "Redis Server - ${redisName name}";
 
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
 
-      preStart = ''
-        install -m 600 ${redisConfig} /run/redis/redis.conf
-      '' + optionalString (cfg.requirePassFile != null) ''
-        password=$(cat ${escapeShellArg cfg.requirePassFile})
-        echo "requirePass $password" >> /run/redis/redis.conf
-      '';
-
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf";
+        ExecStart = "${cfg.package}/bin/redis-server /run/${redisName name}/redis.conf";
+        ExecStartPre = [("+"+pkgs.writeShellScript "${redisName name}-credentials" (''
+            install -o '${conf.user}' -m 600 ${redisConfig conf.settings} /run/${redisName name}/redis.conf
+          '' + optionalString (conf.requirePassFile != null) ''
+            {
+              printf requirePass' '
+              cat ${escapeShellArg conf.requirePassFile}
+            } >>/run/${redisName name}/redis.conf
+          '')
+        )];
         Type = "notify";
         # User and group
-        User = "redis";
-        Group = "redis";
+        User = conf.user;
+        Group = conf.user;
         # Runtime directory and mode
-        RuntimeDirectory = "redis";
+        RuntimeDirectory = redisName name;
         RuntimeDirectoryMode = "0750";
         # State directory and mode
-        StateDirectory = "redis";
+        StateDirectory = redisName name;
         StateDirectoryMode = "0700";
         # Access write directories
         UMask = "0077";
@@ -309,7 +352,7 @@ in {
         # Security
         NoNewPrivileges = true;
         # Process Properties
-        LimitNOFILE = "${toString ulimitNofile}";
+        LimitNOFILE = mkDefault "${toString (conf.maxclients + 32)}";
         # Sandboxing
         ProtectSystem = "strict";
         ProtectHome = true;
@@ -322,7 +365,9 @@ in {
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
         ProtectControlGroups = true;
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictAddressFamilies =
+          optionals (conf.bind != null) ["AF_INET" "AF_INET6"] ++
+          optional (conf.unixSocket != null) "AF_UNIX";
         RestrictNamespaces = true;
         LockPersonality = true;
         MemoryDenyWriteExecute = true;
@@ -333,6 +378,7 @@ in {
         SystemCallArchitectures = "native";
         SystemCallFilter = "~@cpu-emulation @debug @keyring @memlock @mount @obsolete @privileged @resources @setuid";
       };
-    };
+    }) enabledServers;
+
   };
 }
diff --git a/nixpkgs/nixos/modules/services/desktops/gvfs.nix b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
index b6a27279bdf8..cc9a46032705 100644
--- a/nixpkgs/nixos/modules/services/desktops/gvfs.nix
+++ b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
@@ -54,7 +54,7 @@ in
 
     systemd.packages = [ cfg.package ];
 
-    services.udev.packages = [ pkgs.libmtp.bin ];
+    services.udev.packages = [ pkgs.libmtp ];
 
     # Needed for unwrapped applications
     environment.variables.GIO_EXTRA_MODULES = [ "${cfg.package}/lib/gio/modules" ];
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/client-rt.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
index 284d8c394a61..284d8c394a61 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/client-rt.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/client.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
index 71294a0e78a2..71294a0e78a2 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/client.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/client.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/jack.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
index 128178bfa027..128178bfa027 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/jack.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
index 3ed994f11145..3ed994f11145 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
index a923ab4db235..a923ab4db235 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
index 53fc9cc96343..53fc9cc96343 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
index 6d1c23e82569..6d1c23e82569 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
index 4b4e302af387..4b4e302af387 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
index b08cba1b604b..b08cba1b604b 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
index 4ae6aab29cdb..4be3e881a9dc 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
@@ -13,10 +13,10 @@ 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));
-    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 = {
@@ -43,8 +43,8 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.pipewire.mediaSession;
-        defaultText = literalExpression "pkgs.pipewire.mediaSession";
+        default = pkgs.pipewire-media-session;
+        defaultText = literalExpression "pkgs.pipewire-media-session";
         description = ''
           The pipewire-media-session derivation to use.
         '';
@@ -55,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 = {};
         };
@@ -64,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 = {};
         };
@@ -73,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 = {};
         };
@@ -82,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 = {};
         };
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix
index 604645b2b18a..55755ecd6457 100644
--- a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixpkgs/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 = {
diff --git a/nixpkgs/nixos/modules/services/development/blackfire.nix b/nixpkgs/nixos/modules/services/development/blackfire.nix
index 6fd948cce38d..8564aabc6a37 100644
--- a/nixpkgs/nixos/modules/services/development/blackfire.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/development/blackfire.xml b/nixpkgs/nixos/modules/services/development/blackfire.xml
index ad4af35788db..cecd249dda48 100644
--- a/nixpkgs/nixos/modules/services/development/blackfire.xml
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/development/hoogle.nix b/nixpkgs/nixos/modules/services/development/hoogle.nix
index 7c635f7a5b8d..7c2a1c8e1624 100644
--- a/nixpkgs/nixos/modules/services/development/hoogle.nix
+++ b/nixpkgs/nixos/modules/services/development/hoogle.nix
@@ -40,6 +40,7 @@ in {
 
     haskellPackages = mkOption {
       description = "Which haskell package set to use.";
+      type = types.attrs;
       default = pkgs.haskellPackages;
       defaultText = literalExpression "pkgs.haskellPackages";
     };
diff --git a/nixpkgs/nixos/modules/services/finance/odoo.nix b/nixpkgs/nixos/modules/services/finance/odoo.nix
new file mode 100644
index 000000000000..422ee9510074
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/games/factorio.nix b/nixpkgs/nixos/modules/services/games/factorio.nix
index 0e8860a02819..96fcd6d2c8b3 100644
--- a/nixpkgs/nixos/modules/services/games/factorio.nix
+++ b/nixpkgs/nixos/modules/services/games/factorio.nix
@@ -75,8 +75,8 @@ in
         description = ''
           The name of the savegame that will be used by the server.
 
-          When not present in ${stateDir}/saves, a new map with default
-          settings will be generated before starting the service.
+          When not present in /var/lib/''${config.services.factorio.stateDirName}/saves,
+          a new map with default settings will be generated before starting the service.
         '';
       };
       # TODO Add more individual settings as nixos-options?
diff --git a/nixpkgs/nixos/modules/services/games/quake3-server.nix b/nixpkgs/nixos/modules/services/games/quake3-server.nix
index 1dc01260e8fa..175af4a83828 100644
--- a/nixpkgs/nixos/modules/services/games/quake3-server.nix
+++ b/nixpkgs/nixos/modules/services/games/quake3-server.nix
@@ -71,6 +71,7 @@ in {
       baseq3 = mkOption {
         type = types.either types.package types.path;
         default = defaultBaseq3;
+        defaultText = literalDocBook "Manually downloaded Quake 3 installation directory.";
         example = "/var/lib/q3ds";
         description = ''
           Path to the baseq3 files (pak*.pk3). If this is on the nix store (type = package) all .pk3 files should be saved
diff --git a/nixpkgs/nixos/modules/services/games/terraria.nix b/nixpkgs/nixos/modules/services/games/terraria.nix
index 7312c7e6b635..29f976b3c2ae 100644
--- a/nixpkgs/nixos/modules/services/games/terraria.nix
+++ b/nixpkgs/nixos/modules/services/games/terraria.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg   = config.services.terraria;
+  opt   = options.services.terraria;
   worldSizeMap = { small = 1; medium = 2; large = 3; };
   valFlag = name: val: optionalString (val != null) "-${name} \"${escape ["\\" "\""] (toString val)}\"";
   boolFlag = name: val: optionalString val "-${name}";
@@ -36,7 +37,7 @@ in
         type        = types.bool;
         default     = false;
         description = ''
-          If enabled, starts a Terraria server. The server can be connected to via <literal>tmux -S ${cfg.dataDir}/terraria.sock attach</literal>
+          If enabled, starts a Terraria server. The server can be connected to via <literal>tmux -S ''${config.${opt.dataDir}}/terraria.sock attach</literal>
           for administration by users who are a part of the <literal>terraria</literal> group (use <literal>C-b d</literal> shortcut to detach again).
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
index 7f75ac272d40..69a66723e76c 100644
--- a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
@@ -11,12 +11,8 @@ let
 
   cfgFmt = pkgs.formats.ini { };
 
-  # bluez will complain if some of the sections are not found, so just make them
-  # empty (but present in the file) for now
   defaults = {
     General.ControllerMode = "dual";
-    Controller = { };
-    GATT = { };
     Policy.AutoEnable = cfg.powerOnBoot;
   };
 
diff --git a/nixpkgs/nixos/modules/services/hardware/fancontrol.nix b/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
index 5574c5a132e5..861b70970b87 100644
--- a/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/hardware/power-profiles-daemon.nix b/nixpkgs/nixos/modules/services/hardware/power-profiles-daemon.nix
index 70b7a72b8bae..4144bc667088 100644
--- a/nixpkgs/nixos/modules/services/hardware/power-profiles-daemon.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix b/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix
new file mode 100644
index 000000000000..2d4c6d2ce959
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/rasdaemon.nix
@@ -0,0 +1,170 @@
+{ 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" ];
+
+        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/nixpkgs/nixos/modules/services/hardware/spacenavd.nix b/nixpkgs/nixos/modules/services/hardware/spacenavd.nix
index 74725dd23d25..69ca6f102efe 100644
--- a/nixpkgs/nixos/modules/services/hardware/spacenavd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/spacenavd.nix
@@ -15,7 +15,6 @@ in {
   config = mkIf cfg.enable {
     systemd.user.services.spacenavd = {
       description = "Daemon for the Spacenavigator 6DOF mice by 3Dconnexion";
-      after = [ "syslog.target" ];
       wantedBy = [ "graphical.target" ];
       serviceConfig = {
         ExecStart = "${pkgs.spacenavd}/bin/spacenavd -d -l syslog";
diff --git a/nixpkgs/nixos/modules/services/hardware/tcsd.nix b/nixpkgs/nixos/modules/services/hardware/tcsd.nix
index c549a6775013..e414b9647c9b 100644
--- a/nixpkgs/nixos/modules/services/hardware/tcsd.nix
+++ b/nixpkgs/nixos/modules/services/hardware/tcsd.nix
@@ -1,11 +1,12 @@
 # tcsd daemon.
 
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 
 with lib;
 let
 
   cfg = config.services.tcsd;
+  opt = options.services.tcsd;
 
   tcsdConf = pkgs.writeText "tcsd.conf" ''
     port = 30003
@@ -83,6 +84,7 @@ in
 
       platformCred = mkOption {
         default = "${cfg.stateDir}/platform.cert";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/platform.cert"'';
         type = types.path;
         description = ''
           Path to the platform credential for your TPM. Your TPM
@@ -96,6 +98,7 @@ in
 
       conformanceCred = mkOption {
         default = "${cfg.stateDir}/conformance.cert";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/conformance.cert"'';
         type = types.path;
         description = ''
           Path to the conformance credential for your TPM.
@@ -104,6 +107,7 @@ in
 
       endorsementCred = mkOption {
         default = "${cfg.stateDir}/endorsement.cert";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/endorsement.cert"'';
         type = types.path;
         description = ''
           Path to the endorsement credential for your TPM.
diff --git a/nixpkgs/nixos/modules/services/hardware/thinkfan.nix b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
index 7a5a7e1c41ce..4ea829e496e8 100644
--- a/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
@@ -19,7 +19,7 @@ let
         description = "tuple of" + concatMapStrings (t: " (${t.description})") ts;
       };
       level = ints.unsigned;
-      special = enum [ "level auto" "level full-speed" "level disengage" ];
+      special = enum [ "level auto" "level full-speed" "level disengaged" ];
     in
       tuple [ (either level special) level level ];
 
@@ -164,7 +164,7 @@ in {
 
           LEVEL is the fan level to use: it can be an integer (0-7 with thinkpad_acpi),
           "level auto" (to keep the default firmware behavior), "level full-speed" or
-          "level disengage" (to run the fan as fast as possible).
+          "level disengaged" (to run the fan as fast as possible).
           LOW is the temperature at which to step down to the previous level.
           HIGH is the temperature at which to step up to the next level.
           All numbers are integers.
diff --git a/nixpkgs/nixos/modules/services/logging/filebeat.nix b/nixpkgs/nixos/modules/services/logging/filebeat.nix
new file mode 100644
index 000000000000..223a993c505b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/filebeat.nix
@@ -0,0 +1,253 @@
+{ config, lib, utils, pkgs, ... }:
+
+let
+  inherit (lib)
+    attrValues
+    literalExpression
+    mkEnableOption
+    mkIf
+    mkOption
+    types;
+
+  cfg = config.services.filebeat;
+
+  json = pkgs.formats.json {};
+in
+{
+  options = {
+
+    services.filebeat = {
+
+      enable = mkEnableOption "filebeat";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.filebeat;
+        defaultText = literalExpression "pkgs.filebeat";
+        example = literalExpression "pkgs.filebeat7";
+        description = ''
+          The filebeat package to use.
+        '';
+      };
+
+      inputs = mkOption {
+        description = ''
+          Inputs specify how Filebeat locates and processes input data.
+
+          This is like <literal>services.filebeat.settings.filebeat.inputs</literal>,
+          but structured as an attribute set. This has the benefit
+          that multiple NixOS modules can contribute settings to a
+          single filebeat input.
+
+          An input type can be specified multiple times by choosing a
+          different <literal>&lt;name></literal> for each, but setting
+          <xref linkend="opt-services.filebeat.inputs._name_.type"/>
+          to the same value.
+
+          See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html"/>.
+        '';
+        default = {};
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          freeformType = json.type;
+          options = {
+            type = mkOption {
+              type = types.str;
+              default = name;
+              description = ''
+                The input type.
+
+                Look for the value after <literal>type:</literal> on
+                the individual input pages linked from
+                <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html"/>.
+              '';
+            };
+          };
+        }));
+        example = literalExpression ''
+          {
+            journald.id = "everything";  # Only for filebeat7
+            log = {
+              enabled = true;
+              paths = [
+                "/var/log/*.log"
+              ];
+            };
+          };
+        '';
+      };
+
+      modules = mkOption {
+        description = ''
+          Filebeat modules provide a quick way to get started
+          processing common log formats. They contain default
+          configurations, Elasticsearch ingest pipeline definitions,
+          and Kibana dashboards to help you implement and deploy a log
+          monitoring solution.
+
+          This is like <literal>services.filebeat.settings.filebeat.modules</literal>,
+          but structured as an attribute set. This has the benefit
+          that multiple NixOS modules can contribute settings to a
+          single filebeat module.
+
+          A module can be specified multiple times by choosing a
+          different <literal>&lt;name></literal> for each, but setting
+          <xref linkend="opt-services.filebeat.modules._name_.module"/>
+          to the same value.
+
+          See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html"/>.
+        '';
+        default = {};
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          freeformType = json.type;
+          options = {
+            module = mkOption {
+              type = types.str;
+              default = name;
+              description = ''
+                The name of the module.
+
+                Look for the value after <literal>module:</literal> on
+                the individual input pages linked from
+                <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html"/>.
+              '';
+            };
+          };
+        }));
+        example = literalExpression ''
+          {
+            nginx = {
+              access = {
+                enabled = true;
+                var.paths = [ "/path/to/log/nginx/access.log*" ];
+              };
+              error = {
+                enabled = true;
+                var.paths = [ "/path/to/log/nginx/error.log*" ];
+              };
+            };
+          };
+        '';
+      };
+
+      settings = mkOption {
+        type = types.submodule {
+          freeformType = json.type;
+
+          options = {
+
+            output.elasticsearch.hosts = mkOption {
+              type = with types; listOf str;
+              default = [ "127.0.0.1:9200" ];
+              example = [ "myEShost:9200" ];
+              description = ''
+                The list of Elasticsearch nodes to connect to.
+
+                The events are distributed to these nodes in round
+                robin order. If one node becomes unreachable, the
+                event is automatically sent to another node. Each
+                Elasticsearch node can be defined as a URL or
+                IP:PORT. For example:
+                <literal>http://192.15.3.2</literal>,
+                <literal>https://es.found.io:9230</literal> or
+                <literal>192.24.3.2:9300</literal>. If no port is
+                specified, <literal>9200</literal> is used.
+              '';
+            };
+
+            filebeat = {
+              inputs = mkOption {
+                type = types.listOf json.type;
+                default = [];
+                internal = true;
+                description = ''
+                  Inputs specify how Filebeat locates and processes
+                  input data. Use <xref
+                  linkend="opt-services.filebeat.inputs"/> instead.
+
+                  See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html"/>.
+                '';
+              };
+              modules = mkOption {
+                type = types.listOf json.type;
+                default = [];
+                internal = true;
+                description = ''
+                  Filebeat modules provide a quick way to get started
+                  processing common log formats. They contain default
+                  configurations, Elasticsearch ingest pipeline
+                  definitions, and Kibana dashboards to help you
+                  implement and deploy a log monitoring solution.
+
+                  Use <xref linkend="opt-services.filebeat.modules"/> instead.
+
+                  See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html"/>.
+                '';
+              };
+            };
+          };
+        };
+        default = {};
+        example = literalExpression ''
+          {
+            settings = {
+              output.elasticsearch = {
+                hosts = [ "myEShost:9200" ];
+                username = "filebeat_internal";
+                password = { _secret = "/var/keys/elasticsearch_password"; };
+              };
+              logging.level = "info";
+            };
+          };
+        '';
+
+        description = ''
+          Configuration for filebeat. See
+          <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-reference-yml.html"/>
+          for supported values.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute <literal>_secret</literal> - a
+          string pointing to a file containing the value the option
+          should be set to. See the example to get a better picture of
+          this: in the resulting
+          <filename>filebeat.yml</filename> file, the
+          <literal>output.elasticsearch.password</literal>
+          key will be set to the contents of the
+          <filename>/var/keys/elasticsearch_password</filename> file.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.filebeat.settings.filebeat.inputs = attrValues cfg.inputs;
+    services.filebeat.settings.filebeat.modules = attrValues cfg.modules;
+
+    systemd.services.filebeat = {
+      description = "Filebeat log shipper";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "elasticsearch.service" ];
+      after = [ "elasticsearch.service" ];
+      serviceConfig = {
+        ExecStartPre = pkgs.writeShellScript "filebeat-exec-pre" ''
+          set -euo pipefail
+
+          umask u=rwx,g=,o=
+
+          ${utils.genJqSecretsReplacementSnippet
+              cfg.settings
+              "/var/lib/filebeat/filebeat.yml"
+           }
+        '';
+        ExecStart = ''
+          ${cfg.package}/bin/filebeat -e \
+            -c "/var/lib/filebeat/filebeat.yml" \
+            --path.data "/var/lib/filebeat"
+        '';
+        Restart = "always";
+        StateDirectory = "filebeat";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journalbeat.nix b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
index 2d98598c1bee..4035ab48b4b8 100644
--- a/nixpkgs/nixos/modules/services/logging/journalbeat.nix
+++ b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
@@ -5,14 +5,10 @@ with lib;
 let
   cfg = config.services.journalbeat;
 
-  lt6 = builtins.compareVersions cfg.package.version "6" < 0;
-
   journalbeatYml = pkgs.writeText "journalbeat.yml" ''
     name: ${cfg.name}
     tags: ${builtins.toJSON cfg.tags}
 
-    ${optionalString lt6 "journalbeat.cursor_state_file: /var/lib/${cfg.stateDir}/cursor-state"}
-
     ${cfg.extraConfig}
   '';
 
@@ -28,7 +24,6 @@ in
         type = types.package;
         default = pkgs.journalbeat;
         defaultText = literalExpression "pkgs.journalbeat";
-        example = literalExpression "pkgs.journalbeat7";
         description = ''
           The journalbeat package to use
         '';
@@ -58,17 +53,7 @@ in
 
       extraConfig = mkOption {
         type = types.lines;
-        default = optionalString lt6 ''
-          journalbeat:
-            seek_position: cursor
-            cursor_seek_fallback: tail
-            write_cursor_state: true
-            cursor_flush_period: 5s
-            clean_field_names: true
-            convert_to_numbers: false
-            move_metadata_to_field: journal
-            default_type: journal
-        '';
+        default = "";
         description = "Any other configuration options you want to add";
       };
 
@@ -89,6 +74,8 @@ in
     systemd.services.journalbeat = {
       description = "Journalbeat log shipper";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "elasticsearch.service" ];
+      after = [ "elasticsearch.service" ];
       preStart = ''
         mkdir -p ${cfg.stateDir}/data
         mkdir -p ${cfg.stateDir}/logs
diff --git a/nixpkgs/nixos/modules/services/logging/journalwatch.nix b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
index 576c646c0f58..fb86904d1ea2 100644
--- a/nixpkgs/nixos/modules/services/logging/journalwatch.nix
+++ b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
@@ -74,6 +74,7 @@ in {
       mailFrom = mkOption {
         type = types.str;
         default = "journalwatch@${config.networking.hostName}";
+        defaultText = literalExpression ''"journalwatch@''${config.networking.hostName}"'';
         description = ''
           Mail address to send journalwatch reports from.
         '';
diff --git a/nixpkgs/nixos/modules/services/logging/klogd.nix b/nixpkgs/nixos/modules/services/logging/klogd.nix
index 2d1f515da920..8d371c161eb1 100644
--- a/nixpkgs/nixos/modules/services/logging/klogd.nix
+++ b/nixpkgs/nixos/modules/services/logging/klogd.nix
@@ -10,6 +10,7 @@ with lib;
     services.klogd.enable = mkOption {
       type = types.bool;
       default = versionOlder (getVersion config.boot.kernelPackages.kernel) "3.5";
+      defaultText = literalExpression ''versionOlder (getVersion config.boot.kernelPackages.kernel) "3.5"'';
       description = ''
         Whether to enable klogd, the kernel log message processing
         daemon.  Since systemd handles logging of kernel messages on
diff --git a/nixpkgs/nixos/modules/services/logging/logrotate.nix b/nixpkgs/nixos/modules/services/logging/logrotate.nix
index 624b6cfb1215..ba5d6e29d0bd 100644
--- a/nixpkgs/nixos/modules/services/logging/logrotate.nix
+++ b/nixpkgs/nixos/modules/services/logging/logrotate.nix
@@ -40,7 +40,7 @@ let
       };
 
       frequency = mkOption {
-        type = types.enum [ "daily" "weekly" "monthly" "yearly" ];
+        type = types.enum [ "hourly" "daily" "weekly" "monthly" "yearly" ];
         default = "daily";
         description = ''
           How often to rotate the logs.
@@ -155,7 +155,7 @@ in
     systemd.services.logrotate = {
       description = "Logrotate Service";
       wantedBy = [ "multi-user.target" ];
-      startAt = "*-*-* *:05:00";
+      startAt = "hourly";
       script = ''
         exec ${pkgs.logrotate}/sbin/logrotate ${configFile}
       '';
diff --git a/nixpkgs/nixos/modules/services/logging/logstash.nix b/nixpkgs/nixos/modules/services/logging/logstash.nix
index 044d5330231e..a08203dffe78 100644
--- a/nixpkgs/nixos/modules/services/logging/logstash.nix
+++ b/nixpkgs/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
 
@@ -152,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/nixpkgs/nixos/modules/services/mail/dovecot.nix b/nixpkgs/nixos/modules/services/mail/dovecot.nix
index 223f3bef77db..c39827c5b867 100644
--- a/nixpkgs/nixos/modules/services/mail/dovecot.nix
+++ b/nixpkgs/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
         }
       ''
     )
diff --git a/nixpkgs/nixos/modules/services/mail/maddy.nix b/nixpkgs/nixos/modules/services/mail/maddy.nix
new file mode 100644
index 000000000000..0b06905ac6f1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/maddy.nix
@@ -0,0 +1,273 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "maddy";
+
+  cfg = config.services.maddy;
+
+  defaultConfig = ''
+    # Minimal configuration with TLS disabled, adapted from upstream example
+    # configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
+    # Do not use this in production!
+
+    tls off
+
+    auth.pass_table local_authdb {
+      table sql_table {
+        driver sqlite3
+        dsn credentials.db
+        table_name passwords
+      }
+    }
+
+    storage.imapsql local_mailboxes {
+      driver sqlite3
+      dsn imapsql.db
+    }
+
+    table.chain local_rewrites {
+      optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
+      optional_step static {
+        entry postmaster postmaster@$(primary_domain)
+      }
+      optional_step file /etc/maddy/aliases
+    }
+    msgpipeline local_routing {
+      destination postmaster $(local_domains) {
+        modify {
+          replace_rcpt &local_rewrites
+        }
+        deliver_to &local_mailboxes
+      }
+      default_destination {
+        reject 550 5.1.1 "User doesn't exist"
+      }
+    }
+
+    smtp tcp://0.0.0.0:25 {
+      limits {
+        all rate 20 1s
+        all concurrency 10
+      }
+      dmarc yes
+      check {
+        require_mx_record
+        dkim
+        spf
+      }
+      source $(local_domains) {
+        reject 501 5.1.8 "Use Submission for outgoing SMTP"
+      }
+      default_source {
+        destination postmaster $(local_domains) {
+          deliver_to &local_routing
+        }
+        default_destination {
+          reject 550 5.1.1 "User doesn't exist"
+        }
+      }
+    }
+
+    submission tcp://0.0.0.0:587 {
+      limits {
+        all rate 50 1s
+      }
+      auth &local_authdb
+      source $(local_domains) {
+        check {
+            authorize_sender {
+                prepare_email &local_rewrites
+                user_to_email identity
+            }
+        }
+        destination postmaster $(local_domains) {
+            deliver_to &local_routing
+        }
+        default_destination {
+            modify {
+                dkim $(primary_domain) $(local_domains) default
+            }
+            deliver_to &remote_queue
+        }
+      }
+      default_source {
+        reject 501 5.1.8 "Non-local sender domain"
+      }
+    }
+
+    target.remote outbound_delivery {
+      limits {
+        destination rate 20 1s
+        destination concurrency 10
+      }
+      mx_auth {
+        dane
+        mtasts {
+          cache fs
+          fs_dir mtasts_cache/
+        }
+        local_policy {
+            min_tls_level encrypted
+            min_mx_level none
+        }
+      }
+    }
+
+    target.queue remote_queue {
+      target &outbound_delivery
+      autogenerated_msg_domain $(primary_domain)
+      bounce {
+        destination postmaster $(local_domains) {
+          deliver_to &local_routing
+        }
+        default_destination {
+            reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
+        }
+      }
+    }
+
+    imap tcp://0.0.0.0:143 {
+      auth &local_authdb
+      storage &local_mailboxes
+    }
+  '';
+
+in {
+  options = {
+    services.maddy = {
+
+      enable = mkEnableOption "Maddy, a free an open source mail server";
+
+      user = mkOption {
+        default = "maddy";
+        type = with types; uniq string;
+        description = ''
+          User account under which maddy runs.
+
+          <note><para>
+          If left as the default value this user will automatically be created
+          on system activation, otherwise the sysadmin is responsible for
+          ensuring the user exists before the maddy service starts.
+          </para></note>
+        '';
+      };
+
+      group = mkOption {
+        default = "maddy";
+        type = with types; uniq string;
+        description = ''
+          Group account under which maddy runs.
+
+          <note><para>
+          If left as the default value this group will automatically be created
+          on system activation, otherwise the sysadmin is responsible for
+          ensuring the group exists before the maddy service starts.
+          </para></note>
+        '';
+      };
+
+      hostname = mkOption {
+        default = "localhost";
+        type = with types; uniq string;
+        example = ''example.com'';
+        description = ''
+          Hostname to use. It should be FQDN.
+        '';
+      };
+
+      primaryDomain = mkOption {
+        default = "localhost";
+        type = with types; uniq string;
+        example = ''mail.example.com'';
+        description = ''
+          Primary MX domain to use. It should be FQDN.
+        '';
+      };
+
+      localDomains = mkOption {
+        type = with types; listOf str;
+        default = ["$(primary_domain)"];
+        example = [
+          "$(primary_domain)"
+          "example.com"
+          "other.example.com"
+        ];
+        description = ''
+          Define list of allowed domains.
+        '';
+      };
+
+      config = mkOption {
+        type = with types; nullOr lines;
+        default = defaultConfig;
+        description = ''
+          Server configuration, see
+          <link xlink:href="https://maddy.email">https://maddy.email</link> for
+          more information. The default configuration of this module will setup
+          minimal maddy instance for mail transfer without TLS encryption.
+          <note><para>
+          This should not be used in a production environment.
+          </para></note>
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open the configured incoming and outgoing mail server ports.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      packages = [ pkgs.maddy ];
+      services.maddy = {
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+          StateDirectory = [ "maddy" ];
+        };
+        restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
+        wantedBy = [ "multi-user.target" ];
+      };
+    };
+
+    environment.etc."maddy/maddy.conf" = {
+      text = ''
+        $(hostname) = ${cfg.hostname}
+        $(primary_domain) = ${cfg.primaryDomain}
+        $(local_domains) = ${toString cfg.localDomains}
+        hostname ${cfg.hostname}
+        ${cfg.config}
+      '';
+    };
+
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        isSystemUser = true;
+        group = cfg.group;
+        description = "Maddy mail transfer agent user";
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      ${cfg.group} = { };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 25 143 587 ];
+    };
+
+    environment.systemPackages = [
+      pkgs.maddy
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/opendkim.nix b/nixpkgs/nixos/modules/services/mail/opendkim.nix
index beff57613afc..f1ffc5d3aeef 100644
--- a/nixpkgs/nixos/modules/services/mail/opendkim.nix
+++ b/nixpkgs/nixos/modules/services/mail/opendkim.nix
@@ -55,6 +55,7 @@ in {
       domains = mkOption {
         type = types.str;
         default = "csl:${config.networking.hostName}";
+        defaultText = literalExpression ''"csl:''${config.networking.hostName}"'';
         example = "csl:example.com,mydomain.net";
         description = ''
           Local domains set (see <literal>opendkim(8)</literal> for more information on datasets).
diff --git a/nixpkgs/nixos/modules/services/mail/postfix.nix b/nixpkgs/nixos/modules/services/mail/postfix.nix
index 6fc09682e0c0..23d3574ae27c 100644
--- a/nixpkgs/nixos/modules/services/mail/postfix.nix
+++ b/nixpkgs/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";
diff --git a/nixpkgs/nixos/modules/services/mail/roundcube.nix b/nixpkgs/nixos/modules/services/mail/roundcube.nix
index bf5abc7ba556..ac192c56aa60 100644
--- a/nixpkgs/nixos/modules/services/mail/roundcube.nix
+++ b/nixpkgs/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 = {
diff --git a/nixpkgs/nixos/modules/services/mail/rspamd.nix b/nixpkgs/nixos/modules/services/mail/rspamd.nix
index 50208cbeb00a..a570e137a55a 100644
--- a/nixpkgs/nixos/modules/services/mail/rspamd.nix
+++ b/nixpkgs/nixos/modules/services/mail/rspamd.nix
@@ -5,6 +5,7 @@ with lib;
 let
 
   cfg = config.services.rspamd;
+  opt = options.services.rspamd;
   postfixCfg = config.services.postfix;
 
   bindSocketOpts = {options, config, ... }: {
@@ -285,8 +286,8 @@ in
               bindSockets = [{
                 socket = "/run/rspamd/rspamd.sock";
                 mode = "0660";
-                owner = "${cfg.user}";
-                group = "${cfg.group}";
+                owner = "''${config.${opt.user}}";
+                group = "''${config.${opt.group}}";
               }];
             };
             controller = {
diff --git a/nixpkgs/nixos/modules/services/matrix/mjolnir.nix b/nixpkgs/nixos/modules/services/matrix/mjolnir.nix
new file mode 100644
index 000000000000..278924b05cf2
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/matrix/mjolnir.xml b/nixpkgs/nixos/modules/services/matrix/mjolnir.xml
new file mode 100644
index 000000000000..d462ddf7b01b
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/matrix/pantalaimon-options.nix b/nixpkgs/nixos/modules/services/matrix/pantalaimon-options.nix
new file mode 100644
index 000000000000..035c57540d09
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/matrix/pantalaimon.nix b/nixpkgs/nixos/modules/services/matrix/pantalaimon.nix
new file mode 100644
index 000000000000..63b40099ca5d
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/misc/airsonic.nix b/nixpkgs/nixos/modules/services/misc/airsonic.nix
index 533a3d367a32..5a5c30a41233 100644
--- a/nixpkgs/nixos/modules/services/misc/airsonic.nix
+++ b/nixpkgs/nixos/modules/services/misc/airsonic.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.airsonic;
+  opt = options.services.airsonic;
 in {
   options = {
 
@@ -78,7 +79,7 @@ in {
         description = ''
           List of paths to transcoder executables that should be accessible
           from Airsonic. Symlinks will be created to each executable inside
-          ${cfg.home}/transcoders.
+          ''${config.${opt.home}}/transcoders.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/misc/ananicy.nix b/nixpkgs/nixos/modules/services/misc/ananicy.nix
new file mode 100644
index 000000000000..f76f534fb450
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/misc/couchpotato.nix b/nixpkgs/nixos/modules/services/misc/couchpotato.nix
deleted file mode 100644
index f5163cf86cf5..000000000000
--- a/nixpkgs/nixos/modules/services/misc/couchpotato.nix
+++ /dev/null
@@ -1,42 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.couchpotato;
-
-in
-{
-  options = {
-    services.couchpotato = {
-      enable = mkEnableOption "CouchPotato Server";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.couchpotato = {
-      description = "CouchPotato Server";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        Type = "simple";
-        User = "couchpotato";
-        Group = "couchpotato";
-        StateDirectory = "couchpotato";
-        ExecStart = "${pkgs.couchpotato}/bin/couchpotato";
-        Restart = "on-failure";
-      };
-    };
-
-    users.users.couchpotato =
-      { group = "couchpotato";
-        home = "/var/lib/couchpotato/";
-        description = "CouchPotato daemon user";
-        uid = config.ids.uids.couchpotato;
-      };
-
-    users.groups.couchpotato =
-      { gid = config.ids.gids.couchpotato; };
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
index 333ba651cde2..7d9c39a69737 100644
--- a/nixpkgs/nixos/modules/services/misc/dysnomia.nix
+++ b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
@@ -104,31 +104,37 @@ in
       properties = mkOption {
         description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions.";
         default = {};
+        type = types.attrs;
       };
 
       containers = mkOption {
         description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties";
         default = {};
+        type = types.attrsOf types.attrs;
       };
 
       components = mkOption {
         description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state";
         default = {};
+        type = types.attrsOf types.attrs;
       };
 
       extraContainerProperties = mkOption {
         description = "An attribute set providing additional container settings in addition to the default properties";
         default = {};
+        type = types.attrs;
       };
 
       extraContainerPaths = mkOption {
         description = "A list of paths containing additional container configurations that are added to the search folders";
         default = [];
+        type = types.listOf types.path;
       };
 
       extraModulePaths = mkOption {
         description = "A list of paths containing additional modules that are added to the search folders";
         default = [];
+        type = types.listOf types.path;
       };
 
       enableLegacyModules = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/etcd.nix b/nixpkgs/nixos/modules/services/misc/etcd.nix
index c4ea091a0380..3925b7dd1636 100644
--- a/nixpkgs/nixos/modules/services/misc/etcd.nix
+++ b/nixpkgs/nixos/modules/services/misc/etcd.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.etcd;
+  opt = options.services.etcd;
 
 in {
 
@@ -17,12 +18,14 @@ in {
     name = mkOption {
       description = "Etcd unique node name.";
       default = config.networking.hostName;
+      defaultText = literalExpression "config.networking.hostName";
       type = types.str;
     };
 
     advertiseClientUrls = mkOption {
       description = "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
       default = cfg.listenClientUrls;
+      defaultText = literalExpression "config.${opt.listenClientUrls}";
       type = types.listOf types.str;
     };
 
@@ -41,12 +44,14 @@ in {
     initialAdvertisePeerUrls = mkOption {
       description = "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
       default = cfg.listenPeerUrls;
+      defaultText = literalExpression "config.${opt.listenPeerUrls}";
       type = types.listOf types.str;
     };
 
     initialCluster = mkOption {
       description = "Etcd initial cluster configuration for bootstrapping.";
       default = ["${cfg.name}=http://127.0.0.1:2380"];
+      defaultText = literalExpression ''["''${config.${opt.name}}=http://127.0.0.1:2380"]'';
       type = types.listOf types.str;
     };
 
@@ -95,18 +100,21 @@ in {
     peerCertFile = mkOption {
       description = "Cert file to use for peer to peer communication";
       default = cfg.certFile;
+      defaultText = literalExpression "config.${opt.certFile}";
       type = types.nullOr types.path;
     };
 
     peerKeyFile = mkOption {
       description = "Key file to use for peer to peer communication";
       default = cfg.keyFile;
+      defaultText = literalExpression "config.${opt.keyFile}";
       type = types.nullOr types.path;
     };
 
     peerTrustedCaFile = mkOption {
       description = "Certificate authority file to use for peer to peer communication";
       default = cfg.trustedCaFile;
+      defaultText = literalExpression "config.${opt.trustedCaFile}";
       type = types.nullOr types.path;
     };
 
diff --git a/nixpkgs/nixos/modules/services/misc/exhibitor.nix b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
index 28c98edf47af..4c935efbd844 100644
--- a/nixpkgs/nixos/modules/services/misc/exhibitor.nix
+++ b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.exhibitor;
+  opt = options.services.exhibitor;
   exhibitorConfig = ''
     zookeeper-install-directory=${cfg.baseDir}/zookeeper
     zookeeper-data-directory=${cfg.zkDataDir}
@@ -165,6 +166,7 @@ in
       zkDataDir = mkOption {
         type = types.str;
         default = "${cfg.baseDir}/zkData";
+        defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkData"'';
         description = ''
           The Zookeeper data directory
         '';
@@ -172,6 +174,7 @@ in
       zkLogDir = mkOption {
         type = types.path;
         default = "${cfg.baseDir}/zkLogs";
+        defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkLogs"'';
         description = ''
           The Zookeeper logs directory
         '';
diff --git a/nixpkgs/nixos/modules/services/misc/gitea.nix b/nixpkgs/nixos/modules/services/misc/gitea.nix
index c0f7661c5698..0096286701f4 100644
--- a/nixpkgs/nixos/modules/services/misc/gitea.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitea.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.gitea;
+  opt = options.services.gitea;
   gitea = cfg.package;
   pg = config.services.postgresql;
   useMysql = cfg.database.type == "mysql";
@@ -51,6 +52,7 @@ in
       log = {
         rootPath = mkOption {
           default = "${cfg.stateDir}/log";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
           type = types.str;
           description = "Root path for log files.";
         };
@@ -84,6 +86,11 @@ in
         port = mkOption {
           type = types.port;
           default = (if !usePostgresql then 3306 else pg.port);
+          defaultText = literalExpression ''
+            if config.${opt.database.type} != "postgresql"
+            then 3306
+            else config.${options.services.postgresql.port}
+          '';
           description = "Database host port.";
         };
 
@@ -130,6 +137,7 @@ in
         path = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/data/gitea.db";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/gitea.db"'';
           description = "Path to the sqlite3 database file.";
         };
 
@@ -166,6 +174,7 @@ in
         backupDir = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/dump";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"'';
           description = "Path to the dump files.";
         };
       };
@@ -199,6 +208,7 @@ in
         contentDir = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/data/lfs";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"'';
           description = "Where to store LFS files.";
         };
       };
@@ -212,6 +222,7 @@ in
       repositoryRoot = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/repositories";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
         description = "Path to the git repositories.";
       };
 
@@ -299,7 +310,7 @@ in
               ENABLED = true;
               MAILER_TYPE = "sendmail";
               FROM = "do-not-reply@example.org";
-              SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
+              SENDMAIL_PATH = "''${pkgs.system-sendmail}/bin/sendmail";
             };
             other = {
               SHOW_FOOTER_VERSION = false;
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.nix b/nixpkgs/nixos/modules/services/misc/gitlab.nix
index b2abe70627d0..219155777db9 100644
--- a/nixpkgs/nixos/modules/services/misc/gitlab.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, options, pkgs, utils, ... }:
 
 with lib;
 
 let
   cfg = config.services.gitlab;
+  opt = options.services.gitlab;
 
   ruby = cfg.packages.gitlab.ruby;
 
@@ -309,6 +310,7 @@ in {
       backup.path = mkOption {
         type = types.str;
         default = cfg.statePath + "/backup";
+        defaultText = literalExpression ''config.${opt.statePath} + "/backup"'';
         description = "GitLab path for backups.";
       };
 
@@ -475,6 +477,7 @@ in {
       host = mkOption {
         type = types.str;
         default = config.networking.hostName;
+        defaultText = literalExpression "config.networking.hostName";
         description = "GitLab host name. Used e.g. for copy-paste URLs.";
       };
 
@@ -534,6 +537,7 @@ in {
         host = mkOption {
           type = types.str;
           default = config.services.gitlab.host;
+          defaultText = literalExpression "config.services.gitlab.host";
           description = "GitLab container registry host name.";
         };
         port = mkOption {
@@ -552,6 +556,7 @@ in {
         defaultForProjects = mkOption {
           type = types.bool;
           default = cfg.registry.enable;
+          defaultText = literalExpression "config.${opt.registry.enable}";
           description = "If GitLab container registry should be enabled by default for projects.";
         };
         issuer = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/gitweb.nix b/nixpkgs/nixos/modules/services/misc/gitweb.nix
index 13396bf2eb02..a1180716e36b 100644
--- a/nixpkgs/nixos/modules/services/misc/gitweb.nix
+++ b/nixpkgs/nixos/modules/services/misc/gitweb.nix
@@ -47,6 +47,7 @@ in
         $highlight_bin = "${pkgs.highlight}/bin/highlight";
         ${cfg.extraConfig}
       '';
+      defaultText = literalDocBook "generated config file";
       type = types.path;
       readOnly = true;
       internal = true;
diff --git a/nixpkgs/nixos/modules/services/misc/gogs.nix b/nixpkgs/nixos/modules/services/misc/gogs.nix
index d7233f10c7cb..c7ae4f494071 100644
--- a/nixpkgs/nixos/modules/services/misc/gogs.nix
+++ b/nixpkgs/nixos/modules/services/misc/gogs.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.gogs;
+  opt = options.services.gogs;
   configFile = pkgs.writeText "app.ini" ''
     APP_NAME = ${cfg.appName}
     RUN_USER = ${cfg.user}
@@ -129,6 +130,7 @@ in
         path = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/data/gogs.db";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/gogs.db"'';
           description = "Path to the sqlite3 database file.";
         };
       };
@@ -142,6 +144,7 @@ in
       repositoryRoot = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/repositories";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
         description = "Path to the git repositories.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/misc/gollum.nix b/nixpkgs/nixos/modules/services/misc/gollum.nix
index 4053afa69be5..cad73a871ba6 100644
--- a/nixpkgs/nixos/modules/services/misc/gollum.nix
+++ b/nixpkgs/nixos/modules/services/misc/gollum.nix
@@ -100,6 +100,7 @@ in
       serviceConfig = {
         User = config.users.users.gollum.name;
         Group = config.users.groups.gollum.name;
+        WorkingDirectory = cfg.stateDir;
         ExecStart = ''
           ${pkgs.gollum}/bin/gollum \
             --port ${toString cfg.port} \
diff --git a/nixpkgs/nixos/modules/services/misc/headphones.nix b/nixpkgs/nixos/modules/services/misc/headphones.nix
index 3ee0a4458bd0..31bd61cb4c20 100644
--- a/nixpkgs/nixos/modules/services/misc/headphones.nix
+++ b/nixpkgs/nixos/modules/services/misc/headphones.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -7,6 +7,7 @@ let
   name = "headphones";
 
   cfg = config.services.headphones;
+  opt = options.services.headphones;
 
 in
 
@@ -29,6 +30,7 @@ in
       configFile = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/config.ini";
+        defaultText = literalExpression ''"''${config.${opt.dataDir}}/config.ini"'';
         description = "Path to config file.";
       };
       host = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
index 8279d075bafb..2de25d87ed39 100644
--- a/nixpkgs/nixos/modules/services/misc/home-assistant.nix
+++ b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
@@ -24,6 +24,8 @@ let
 
   availableComponents = cfg.package.availableComponents;
 
+  explicitComponents = cfg.package.extraComponents;
+
   usedPlatforms = config:
     if isAttrs config then
       optional (config ? platform) config.platform
@@ -42,10 +44,13 @@ let
   # } ];
   useComponentPlatform = component: elem component (usedPlatforms cfg.config);
 
-  # Returns whether component is used in config
+  useExplicitComponent = component: elem component explicitComponents;
+
+  # Returns whether component is used in config or explicitly passed into package
   useComponent = component:
     hasAttrByPath (splitString "." component) cfg.config
-    || useComponentPlatform component;
+    || useComponentPlatform component
+    || useExplicitComponent component;
 
   # List of components used in config
   extraComponents = filter useComponent availableComponents;
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix b/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
index c448614eca32..8a8c7f41e3cb 100644
--- a/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
+++ b/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 
 with lib;
 
@@ -7,6 +7,7 @@ let
   registrationFile = "${dataDir}/discord-registration.yaml";
   appDir = "${pkgs.matrix-appservice-discord}/${pkgs.matrix-appservice-discord.passthru.nodeAppDir}";
   cfg = config.services.matrix-appservice-discord;
+  opt = options.services.matrix-appservice-discord;
   # TODO: switch to configGen.json once RFC42 is implemented
   settingsFile = pkgs.writeText "matrix-appservice-discord-settings.json" (builtins.toJSON cfg.settings);
 
@@ -74,6 +75,7 @@ in {
       url = mkOption {
         type = types.str;
         default = "http://localhost:${toString cfg.port}";
+        defaultText = literalExpression ''"http://localhost:''${toString config.${opt.port}}"'';
         description = ''
           The URL where the application service is listening for HS requests.
         '';
@@ -98,6 +100,9 @@ in {
       serviceDependencies = mkOption {
         type = with types; listOf str;
         default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        defaultText = literalExpression ''
+          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+        '';
         description = ''
           List of Systemd services to require and wait for when starting the application service,
           such as the Matrix homeserver if it's running on the same host.
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
index 950c72c6e589..404163d2de6c 100644
--- a/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.matrix-synapse;
+  opt = options.services.matrix-synapse;
   pg = config.services.postgresql;
   usePostgresql = cfg.database_type == "psycopg2";
   logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
@@ -197,7 +198,7 @@ in {
       tls_certificate_path = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "${cfg.dataDir}/homeserver.tls.crt";
+        example = "/var/lib/matrix-synapse/homeserver.tls.crt";
         description = ''
           PEM encoded X509 certificate for TLS.
           You can replace the self-signed certificate that synapse
@@ -209,7 +210,7 @@ in {
       tls_private_key_path = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "${cfg.dataDir}/homeserver.tls.key";
+        example = "/var/lib/matrix-synapse/homeserver.tls.key";
         description = ''
           PEM encoded private key for TLS. Specify null if synapse is not
           speaking TLS directly.
@@ -218,7 +219,7 @@ in {
       tls_dh_params_path = mkOption {
         type = types.nullOr types.str;
         default = null;
-        example = "${cfg.dataDir}/homeserver.tls.dh";
+        example = "/var/lib/matrix-synapse/homeserver.tls.dh";
         description = ''
           PEM dh parameters for ephemeral keys
         '';
@@ -227,6 +228,7 @@ in {
         type = types.str;
         example = "example.com";
         default = config.networking.hostName;
+        defaultText = literalExpression "config.networking.hostName";
         description = ''
           The domain name of the server, with optional explicit port.
           This is used by remote servers to look up the server address.
@@ -379,6 +381,11 @@ in {
         default = if versionAtLeast config.system.stateVersion "18.03"
           then "psycopg2"
           else "sqlite3";
+        defaultText = literalExpression ''
+          if versionAtLeast config.system.stateVersion "18.03"
+            then "psycopg2"
+            else "sqlite3"
+        '';
         description = ''
           The database engine name. Can be sqlite or psycopg2.
         '';
@@ -402,6 +409,29 @@ in {
             database = cfg.database_name;
           };
         }.${cfg.database_type};
+        defaultText = literalDocBook ''
+          <variablelist>
+            <varlistentry>
+              <term>using sqlite3</term>
+              <listitem>
+                <programlisting>
+                  { database = "''${config.${opt.dataDir}}/homeserver.db"; }
+                </programlisting>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>using psycopg2</term>
+              <listitem>
+                <programlisting>
+                  psycopg2 = {
+                    user = config.${opt.database_user};
+                    database = config.${opt.database_name};
+                  }
+                </programlisting>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        '';
         description = ''
           Arguments to pass to the engine.
         '';
@@ -733,7 +763,7 @@ in {
       after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        ${cfg.package}/bin/homeserver \
+        ${cfg.package}/bin/synapse_homeserver \
           --config-path ${configFile} \
           --keys-directory ${cfg.dataDir} \
           --generate-keys
@@ -753,7 +783,7 @@ in {
           chmod 0600 ${cfg.dataDir}/homeserver.signing.key
         '')) ];
         ExecStart = ''
-          ${cfg.package}/bin/homeserver \
+          ${cfg.package}/bin/synapse_homeserver \
             ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
             --keys-directory ${cfg.dataDir}
         '';
diff --git a/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix b/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
index 59d0b6824090..794c4dd9ddcd 100644
--- a/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
+++ b/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
@@ -108,6 +108,9 @@ in {
       serviceDependencies = mkOption {
         type = with types; listOf str;
         default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        defaultText = literalExpression ''
+          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+        '';
         description = ''
           List of Systemd services to require and wait for when starting the application service.
         '';
@@ -142,7 +145,7 @@ in {
             --config='${settingsFile}' \
             --registration='${registrationFile}'
         fi
-
+      '' + lib.optionalString (pkgs.mautrix-telegram ? alembic) ''
         # run automatic database init and migration scripts
         ${pkgs.mautrix-telegram.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/mediatomb.nix b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
index 383090575b22..ea9ffbb86775 100644
--- a/nixpkgs/nixos/modules/services/misc/mediatomb.nix
+++ b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -6,6 +6,7 @@ let
 
   gid = config.ids.gids.mediatomb;
   cfg = config.services.mediatomb;
+  opt = options.services.mediatomb;
   name = cfg.package.pname;
   pkg = cfg.package;
   optionYesNo = option: if option then "yes" else "no";
@@ -261,6 +262,7 @@ in {
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/${name}";
+        defaultText = literalExpression ''"/var/lib/''${config.${opt.package}.pname}"'';
         description = ''
           The directory where Gerbera/Mediatomb stores its state, data, etc.
         '';
@@ -277,13 +279,13 @@ in {
       user = mkOption {
         type = types.str;
         default = "mediatomb";
-        description = "User account under which ${name} runs.";
+        description = "User account under which the service runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "mediatomb";
-        description = "Group account under which ${name} runs.";
+        description = "Group account under which the service runs.";
       };
 
       port = mkOption {
@@ -340,7 +342,7 @@ in {
         type = types.bool;
         default = false;
         description = ''
-          Allow ${name} to create and use its own config file inside the <literal>dataDir</literal> as
+          Allow the service to create and use its own config file inside the <literal>dataDir</literal> as
           configured by <option>services.mediatomb.dataDir</option>.
           Deactivated by default, the service then runs with the configuration generated from this module.
           Otherwise, when enabled, no service configuration is generated. Gerbera/Mediatomb then starts using
diff --git a/nixpkgs/nixos/modules/services/misc/moonraker.nix b/nixpkgs/nixos/modules/services/misc/moonraker.nix
index de8668a0c066..ae57aaa6d479 100644
--- a/nixpkgs/nixos/modules/services/misc/moonraker.nix
+++ b/nixpkgs/nixos/modules/services/misc/moonraker.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 with lib;
 let
   pkg = pkgs.moonraker;
   cfg = config.services.moonraker;
+  opt = options.services.moonraker;
   format = pkgs.formats.ini {
     # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
     listToValue = l:
@@ -18,6 +19,7 @@ in {
       klipperSocket = mkOption {
         type = types.path;
         default = config.services.klipper.apiSocket;
+        defaultText = literalExpression "config.services.klipper.apiSocket";
         description = "Path to Klipper's API socket.";
       };
 
@@ -30,6 +32,7 @@ in {
       configDir = mkOption {
         type = types.path;
         default = cfg.stateDir + "/config";
+        defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
         description = ''
           The directory containing client-writable configuration files.
 
diff --git a/nixpkgs/nixos/modules/services/misc/mwlib.nix b/nixpkgs/nixos/modules/services/misc/mwlib.nix
deleted file mode 100644
index 8dd17c06c0b3..000000000000
--- a/nixpkgs/nixos/modules/services/misc/mwlib.nix
+++ /dev/null
@@ -1,258 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.mwlib;
-  pypkgs = pkgs.python27Packages;
-
-  inherit (pypkgs) python mwlib;
-
-  user = mkOption {
-    default = "nobody";
-    type = types.str;
-    description = "User to run as.";
-  };
-
-in
-{
-
-  options.services.mwlib = {
-
-    nserve = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Whether to enable nserve. Nserve is a HTTP
-          server.  The Collection extension is talking to
-          that program directly.  Nserve uses at least
-          one qserve instance in order to distribute
-          and manage jobs.
-        '';
-      }; # nserve.enable
-
-      port = mkOption {
-        default = 8899;
-        type = types.port;
-        description = "Specify port to listen on.";
-      }; # nserve.port
-
-      address = mkOption {
-        default = "127.0.0.1";
-        type = types.str;
-        description = "Specify network interface to listen on.";
-      }; # nserve.address
-
-      qserve = mkOption {
-        default = [ "${cfg.qserve.address}:${toString cfg.qserve.port}" ];
-        type = types.listOf types.str;
-        description = "Register qserve instance.";
-      }; # nserve.qserve
-
-      inherit user;
-    }; # nserve
-
-    qserve = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          A job queue server used to distribute and manage
-          jobs. You should start one qserve instance
-          for each machine that is supposed to render pdf
-          files. Unless you’re operating the Wikipedia
-          installation, one machine should suffice.
-        '';
-      }; # qserve.enable
-
-      port = mkOption {
-        default = 14311;
-        type = types.port;
-        description = "Specify port to listen on.";
-      }; # qserve.port
-
-      address = mkOption {
-        default = "127.0.0.1";
-        type = types.str;
-        description = "Specify network interface to listen on.";
-      }; # qserve.address
-
-      datadir = mkOption {
-        default = "/var/lib/mwlib-qserve";
-        type = types.path;
-        description = "qserve data directory (FIXME: unused?)";
-      }; # qserve.datadir
-
-      allow = mkOption {
-        default = [ "127.0.0.1" ];
-        type = types.listOf types.str;
-        description = "List of allowed client IPs. Empty means any.";
-      }; # qserve.allow
-
-      inherit user;
-    }; # qserve
-
-    nslave = {
-      enable = mkOption {
-        default = cfg.qserve.enable;
-        type = types.bool;
-        description = ''
-          Pulls new jobs from exactly one qserve instance
-          and calls the zip and render programs
-          in order to download article collections and
-          convert them to different output formats. Nslave
-          uses a cache directory to store the generated
-          documents. Nslave also starts an internal http
-          server serving the content of the cache directory.
-        '';
-      }; # nslave.enable
-
-      cachedir = mkOption {
-        default = "/var/cache/mwlib-nslave";
-        type = types.path;
-        description = "Directory to store generated documents.";
-      }; # nslave.cachedir
-
-      numprocs = mkOption {
-        default = 10;
-        type = types.int;
-        description = "Number of parallel jobs to be executed.";
-      }; # nslave.numprocs
-
-      http = mkOption {
-        default = {};
-        description = ''
-          Internal http server serving the content of the cache directory.
-          You have to enable it, or use your own way for serving files
-          and set the http.url option accordingly.
-          '';
-        type = types.submodule ({
-          options = {
-            enable = mkOption {
-              default = true;
-              type = types.bool;
-              description = "Enable internal http server.";
-            }; # nslave.http.enable
-
-            port = mkOption {
-              default = 8898;
-              type = types.port;
-              description = "Port to listen to when serving files from cache.";
-            }; # nslave.http.port
-
-            address = mkOption {
-              default = "127.0.0.1";
-              type = types.str;
-              description = "Specify network interface to listen on.";
-            }; # nslave.http.address
-
-            url = mkOption {
-              default = "http://localhost:${toString cfg.nslave.http.port}/cache";
-              type = types.str;
-              description = ''
-                Specify URL for accessing generated files from cache.
-                The Collection extension of Mediawiki won't be able to
-                download files without it.
-                '';
-            }; # nslave.http.url
-          };
-        }); # types.submodule
-      }; # nslave.http
-
-      inherit user;
-    }; # nslave
-
-  }; # options.services
-
-  config = {
-
-    systemd.services.mwlib-nserve = mkIf cfg.nserve.enable
-    {
-      description = "mwlib network interface";
-
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "mwlib-qserve.service" ];
-
-      serviceConfig = {
-        ExecStart = concatStringsSep " " (
-          [
-            "${mwlib}/bin/nserve"
-            "--port ${toString cfg.nserve.port}"
-            "--interface ${cfg.nserve.address}"
-          ] ++ cfg.nserve.qserve
-        );
-        User = cfg.nserve.user;
-      };
-    }; # systemd.services.mwlib-nserve
-
-    systemd.services.mwlib-qserve = mkIf cfg.qserve.enable
-    {
-      description = "mwlib job queue server";
-
-      wantedBy = [ "multi-user.target" ];
-
-      preStart = ''
-        mkdir -pv '${cfg.qserve.datadir}'
-        chown -Rc ${cfg.qserve.user}:`id -ng ${cfg.qserve.user}` '${cfg.qserve.datadir}'
-        chmod -Rc u=rwX,go= '${cfg.qserve.datadir}'
-      '';
-
-      serviceConfig = {
-        ExecStart = concatStringsSep " " (
-          [
-            "${mwlib}/bin/mw-qserve"
-            "-p ${toString cfg.qserve.port}"
-            "-i ${cfg.qserve.address}"
-            "-d ${cfg.qserve.datadir}"
-          ] ++ map (a: "-a ${a}") cfg.qserve.allow
-        );
-        User = cfg.qserve.user;
-        PermissionsStartOnly = true;
-      };
-    }; # systemd.services.mwlib-qserve
-
-    systemd.services.mwlib-nslave = mkIf cfg.nslave.enable
-    {
-      description = "mwlib worker";
-
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-
-      preStart = ''
-        mkdir -pv '${cfg.nslave.cachedir}'
-        chown -Rc ${cfg.nslave.user}:`id -ng ${cfg.nslave.user}` '${cfg.nslave.cachedir}'
-        chmod -Rc u=rwX,go= '${cfg.nslave.cachedir}'
-      '';
-
-      path = with pkgs; [ imagemagick pdftk ];
-      environment = {
-        PYTHONPATH = concatMapStringsSep ":"
-          (m: "${pypkgs.${m}}/lib/${python.libPrefix}/site-packages")
-          [ "mwlib-rl" "mwlib-ext" "pygments" "pyfribidi" ];
-      };
-
-      serviceConfig = {
-        ExecStart = concatStringsSep " " (
-          [
-            "${mwlib}/bin/nslave"
-            "--cachedir ${cfg.nslave.cachedir}"
-            "--numprocs ${toString cfg.nslave.numprocs}"
-            "--url ${cfg.nslave.http.url}"
-          ] ++ (
-            if cfg.nslave.http.enable then
-            [
-              "--serve-files-port ${toString cfg.nslave.http.port}"
-              "--serve-files-address ${cfg.nslave.http.address}"
-            ] else
-            [
-              "--no-serve-files"
-            ]
-          ));
-        User = cfg.nslave.user;
-        PermissionsStartOnly = true;
-      };
-    }; # systemd.services.mwlib-nslave
-
-  }; # config
-}
diff --git a/nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix b/nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix
index c34803f97223..b6f5e04511ae 100644
--- a/nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix
+++ b/nixpkgs/nixos/modules/services/misc/mx-puppet-discord.nix
@@ -39,7 +39,7 @@ in {
 
           #defaults to sqlite but can be configured to use postgresql with
           #connstring
-          database.filename = "${dataDir}/mx-puppet-discord/database.db";
+          database.filename = "${dataDir}/database.db";
           logging = {
             console = "info";
             lineDateFormat = "MMM-D HH:mm:ss.SSS";
@@ -67,6 +67,9 @@ in {
       serviceDependencies = mkOption {
         type = with types; listOf str;
         default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        defaultText = literalExpression ''
+          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+        '';
         description = ''
           List of Systemd services to require and wait for when starting the application service.
         '';
@@ -110,7 +113,9 @@ in {
         UMask = 0027;
 
         ExecStart = ''
-          ${pkgs.mx-puppet-discord}/bin/mx-puppet-discord -c ${settingsFile}
+          ${pkgs.mx-puppet-discord}/bin/mx-puppet-discord \
+            -c ${settingsFile} \
+            -f ${registrationFile}
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/misc/nitter.nix b/nixpkgs/nixos/modules/services/misc/nitter.nix
index 0c562343d85d..6a9eeb02095c 100644
--- a/nixpkgs/nixos/modules/services/misc/nitter.nix
+++ b/nixpkgs/nixos/modules/services/misc/nitter.nix
@@ -299,7 +299,7 @@ in
     systemd.services.nitter = {
         description = "Nitter (An alternative Twitter front-end)";
         wantedBy = [ "multi-user.target" ];
-        after = [ "syslog.target" "network.target" ];
+        after = [ "network.target" ];
         serviceConfig = {
           DynamicUser = true;
           StateDirectory = "nitter";
diff --git a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
index d04937004777..869feb05eb7b 100644
--- a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
@@ -74,6 +74,8 @@ in
   imports = [
     (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
     (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
+    (mkRenamedOptionModule [ "nix" "daemonIONiceLevel" ] [ "nix" "daemonIOSchedPriority" ])
+    (mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.")
   ];
 
   ###### interface
@@ -184,34 +186,71 @@ 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. Note that nix
-          bypasses nix-daemon when running as root and this option does not have
-          any effect in such a case.
-
-          Please note that if used on a recent Linux kernel with group scheduling,
-          setting the nice level will only have an effect relative to other threads
-          in the same task group. Therefore this option is only useful if
-          autogrouping has been disabled (see the kernel.sched_autogroup_enabled
-          sysctl) and no systemd unit uses any of the per-service CPU accounting
-          features of systemd. Otherwise the Nix daemon process may be placed in a
-          separate task group and the nice level setting will have no effect.
-          Refer to the man pages sched(7) and systemd.resource-control(5) for
-          details.
-        '';
+          Nix daemon process CPU scheduling policy. This policy propagates to
+          build processes. <literal>other</literal> is the default scheduling
+          policy for regular tasks. The <literal>batch</literal> policy is
+          similar to <literal>other</literal>, but optimised for
+          non-interactive tasks. <literal>idle</literal> is for extremely
+          low-priority tasks that should only be run when no other task
+          requires CPU time.
+
+          Please note that while using the <literal>idle</literal> policy may
+          greatly improve responsiveness of a system performing expensive
+          builds, it may also slow down and potentially starve crucial
+          configuration updates during load.
+
+          <literal>idle</literal> may therefore be a sensible policy for
+          systems that experience only intermittent phases of high CPU load,
+          such as desktop or portable computers used interactively. Other
+          systems should use the <literal>other</literal> or
+          <literal>batch</literal> policy instead.
+
+          For more fine-grained resource control, please refer to
+          <citerefentry><refentrytitle>systemd.resource-control
+          </refentrytitle><manvolnum>5</manvolnum></citerefentry> and adjust
+          <option>systemd.services.nix-daemon</option> directly.
+      '';
+      };
+
+      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. <literal>best-effort</literal> is the default
+          class for regular tasks. The <literal>idle</literal> class is for
+          extremely low-priority tasks that should only perform I/O when no
+          other task does.
+
+          Please note that while using the <literal>idle</literal> scheduling
+          class can improve responsiveness of a system performing expensive
+          builds, it might also slow down or starve crucial configuration
+          updates during load.
+
+          <literal>idle</literal> may therefore be a sensible class for
+          systems that experience only intermittent phases of high I/O load,
+          such as desktop or portable computers used interactively. Other
+          systems should use the <literal>best-effort</literal> class.
+      '';
       };
 
-      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 {
@@ -529,7 +568,7 @@ in
       [ nix
         pkgs.nix-info
       ]
-      ++ optional (config.programs.bash.enableCompletion && !versionAtLeast nixVersion "2.4pre") pkgs.nix-bash-completions;
+      ++ optional (config.programs.bash.enableCompletion) pkgs.nix-bash-completions;
 
     environment.etc."nix/nix.conf".source = nixConf;
 
@@ -587,8 +626,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/nixpkgs/nixos/modules/services/misc/plex.nix b/nixpkgs/nixos/modules/services/misc/plex.nix
index 5f99ee866a50..2ae4e80d5c3f 100644
--- a/nixpkgs/nixos/modules/services/misc/plex.nix
+++ b/nixpkgs/nixos/modules/services/misc/plex.nix
@@ -65,6 +65,29 @@ 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;
@@ -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/nixpkgs/nixos/modules/services/misc/rippled.nix b/nixpkgs/nixos/modules/services/misc/rippled.nix
index 9c66df2fce1c..f6ec0677774b 100644
--- a/nixpkgs/nixos/modules/services/misc/rippled.nix
+++ b/nixpkgs/nixos/modules/services/misc/rippled.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.rippled;
+  opt = options.services.rippled;
 
   b2i = val: if val then "1" else "0";
 
@@ -165,6 +166,7 @@ let
         description = "Location to store the database.";
         type = types.path;
         default = cfg.databasePath;
+        defaultText = literalExpression "config.${opt.databasePath}";
       };
 
       compression = mkOption {
@@ -177,6 +179,7 @@ let
         description = "Enable automatic purging of older ledger information.";
         type = types.nullOr (types.addCheck types.int (v: v > 256));
         default = cfg.ledgerHistory;
+        defaultText = literalExpression "config.${opt.ledgerHistory}";
       };
 
       advisoryDelete = mkOption {
@@ -398,6 +401,7 @@ in
       config = mkOption {
         internal = true;
         default = pkgs.writeText "rippled.conf" rippledCfg;
+        defaultText = literalDocBook "generated config file";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/misc/sickbeard.nix b/nixpkgs/nixos/modules/services/misc/sickbeard.nix
index 8e871309c98e..a3db99286342 100644
--- a/nixpkgs/nixos/modules/services/misc/sickbeard.nix
+++ b/nixpkgs/nixos/modules/services/misc/sickbeard.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -7,6 +7,7 @@ let
   name = "sickbeard";
 
   cfg = config.services.sickbeard;
+  opt = options.services.sickbeard;
   sickbeard = cfg.package;
 
 in
@@ -39,6 +40,7 @@ in
       configFile = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/config.ini";
+        defaultText = literalExpression ''"''${config.${opt.dataDir}}/config.ini"'';
         description = "Path to config file.";
       };
       port = mkOption {
diff --git a/nixpkgs/nixos/modules/services/misc/signald.nix b/nixpkgs/nixos/modules/services/misc/signald.nix
new file mode 100644
index 000000000000..4cd34e4326d7
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/misc/sourcehut/builds.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/builds.nix
index f806e8c51b99..685a132d3507 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/builds.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/builds.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   scfg = cfg.builds;
   rcfg = config.services.redis;
   iniKey = "builds.sr.ht";
@@ -38,6 +39,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/buildsrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/buildsrht"'';
       description = ''
         State path for builds.sr.ht.
       '';
@@ -61,7 +63,7 @@ in
               rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
               ref = "nixos-unstable";
           };
-          image_from_nixpkgs = pkgs_unstable: (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
+          image_from_nixpkgs = pkgs_unstable: (import ("''${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
             pkgs = (import pkgs_unstable {});
           });
         in
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix
index 9c812d6b043c..1bd21c278e00 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/default.nix
@@ -1,14 +1,90 @@
 { config, pkgs, lib, ... }:
-
 with lib;
 let
+  inherit (config.services) nginx postfix postgresql redis;
+  inherit (config.users) users groups;
   cfg = config.services.sourcehut;
-  cfgIni = cfg.settings;
-  settingsFormat = pkgs.formats.ini { };
+  domain = cfg.settings."sr.ht".global-domain;
+  settingsFormat = pkgs.formats.ini {
+    listToValue = concatMapStringsSep "," (generators.mkValueStringDefault {});
+    mkKeyValue = k: v:
+      if v == null then ""
+      else generators.mkKeyValueDefault {
+        mkValueString = v:
+          if v == true then "yes"
+          else if v == false then "no"
+          else generators.mkValueStringDefault {} v;
+      } "=" k v;
+  };
+  configIniOfService = srv: settingsFormat.generate "sourcehut-${srv}-config.ini"
+    # Each service needs access to only a subset of sections (and secrets).
+    (filterAttrs (k: v: v != null)
+    (mapAttrs (section: v:
+      let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht(::.*)?$" section; in
+      if srvMatch == null # Include sections shared by all services
+      || head srvMatch == srv # Include sections for the service being configured
+      then v
+      # Enable Web links and integrations between services.
+      else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
+      then {
+        inherit (v) origin;
+        # mansrht crashes without it
+        oauth-client-id = v.oauth-client-id or null;
+      }
+      # Drop sub-sections of other services
+      else null)
+    (recursiveUpdate cfg.settings {
+      # Those paths are mounted using BindPaths= or BindReadOnlyPaths=
+      # for services needing access to them.
+      "builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht-worker";
+      "git.sr.ht".post-update-script = "/usr/bin/gitsrht-update-hook";
+      "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
+      "hg.sr.ht".changegroup-script = "/usr/bin/hgsrht-hook-changegroup";
+      "hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
+      # Making this a per service option despite being in a global section,
+      # so that it uses the redis-server used by the service.
+      "sr.ht".redis-host = cfg.${srv}.redis.host;
+    })));
+  commonServiceSettings = srv: {
+    origin = mkOption {
+      description = "URL ${srv}.sr.ht is being served at (protocol://domain)";
+      type = types.str;
+      default = "https://${srv}.${domain}";
+      defaultText = "https://${srv}.example.com";
+    };
+    debug-host = mkOption {
+      description = "Address to bind the debug server to.";
+      type = with types; nullOr str;
+      default = null;
+    };
+    debug-port = mkOption {
+      description = "Port to bind the debug server to.";
+      type = with types; nullOr str;
+      default = null;
+    };
+    connection-string = mkOption {
+      description = "SQLAlchemy connection string for the database.";
+      type = types.str;
+      default = "postgresql:///localhost?user=${srv}srht&host=/run/postgresql";
+    };
+    migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade" // { default = true; };
+    oauth-client-id = mkOption {
+      description = "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
+      type = types.str;
+    };
+    oauth-client-secret = mkOption {
+      description = "${srv}.sr.ht's OAuth client secret for meta.sr.ht.";
+      type = types.path;
+      apply = s: "<" + toString s;
+    };
+  };
 
   # Specialized python containing all the modules
   python = pkgs.sourcehut.python.withPackages (ps: with ps; [
     gunicorn
+    eventlet
+    # For monitoring Celery: sudo -u listssrht celery --app listssrht.process -b redis+socket:///run/redis-sourcehut/redis.sock?virtual_host=5 flower
+    flower
     # Sourcehut services
     srht
     buildsrht
@@ -19,69 +95,37 @@ let
     listssrht
     mansrht
     metasrht
+    # Not a python package
+    #pagessrht
     pastesrht
     todosrht
   ]);
+  mkOptionNullOrStr = description: mkOption {
+    inherit description;
+    type = with types; nullOr str;
+    default = null;
+  };
 in
 {
-  imports =
-    [
-      ./git.nix
-      ./hg.nix
-      ./hub.nix
-      ./todo.nix
-      ./man.nix
-      ./meta.nix
-      ./paste.nix
-      ./builds.nix
-      ./lists.nix
-      ./dispatch.nix
-      (mkRemovedOptionModule [ "services" "sourcehut" "nginx" "enable" ] ''
-        The sourcehut module supports `nginx` as a local reverse-proxy by default and doesn't
-        support other reverse-proxies officially.
-
-        However it's possible to use an alternative reverse-proxy by
-
-          * disabling nginx
-          * adjusting the relevant settings for server addresses and ports directly
-
-        Further details about this can be found in the `Sourcehut`-section of the NixOS-manual.
-      '')
-    ];
-
   options.services.sourcehut = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
-        task dispatching, wiki and account management services
-      '';
-    };
+    enable = mkEnableOption ''
+      sourcehut - git hosting, continuous integration, mailing list, ticket tracking,
+      task dispatching, wiki and account management services
+    '';
 
     services = mkOption {
-      type = types.nonEmptyListOf (types.enum [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ]);
-      default = [ "man" "meta" "paste" ];
-      example = [ "builds" "dispatch" "git" "hub" "hg" "lists" "man" "meta" "paste" "todo" ];
+      type = with types; listOf (enum
+        [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
+      defaultText = "locally enabled services";
       description = ''
-        Services to enable on the sourcehut network.
+        Services that may be displayed as links in the title bar of the Web interface.
       '';
     };
 
-    originBase = mkOption {
+    listenAddress = mkOption {
       type = types.str;
-      default = with config.networking; hostName + lib.optionalString (domain != null) ".${domain}";
-      description = ''
-        Host name used by reverse-proxy and for default settings. Will host services at git."''${originBase}". For example: git.sr.ht
-      '';
-    };
-
-    address = mkOption {
-      type = types.str;
-      default = "127.0.0.1";
-      description = ''
-        Address to bind to.
-      '';
+      default = "localhost";
+      description = "Address to bind to.";
     };
 
     python = mkOption {
@@ -94,105 +138,1247 @@ in
       '';
     };
 
-    statePath = mkOption {
-      type = types.path;
-      default = "/var/lib/sourcehut";
-      description = ''
-        Root state path for the sourcehut network. If left as the default value
-        this directory will automatically be created before the sourcehut server
-        starts, otherwise the sysadmin is responsible for ensuring the
-        directory exists with appropriate ownership and permissions.
-      '';
+    minio = {
+      enable = mkEnableOption ''local minio integration'';
+    };
+
+    nginx = {
+      enable = mkEnableOption ''local nginx integration'';
+      virtualHost = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
+      };
+    };
+
+    postfix = {
+      enable = mkEnableOption ''local postfix integration'';
+    };
+
+    postgresql = {
+      enable = mkEnableOption ''local postgresql integration'';
+    };
+
+    redis = {
+      enable = mkEnableOption ''local redis integration in a dedicated redis-server'';
     };
 
     settings = mkOption {
       type = lib.types.submodule {
         freeformType = settingsFormat.type;
+        options."sr.ht" = {
+          global-domain = mkOption {
+            description = "Global domain name.";
+            type = types.str;
+            example = "example.com";
+          };
+          environment = mkOption {
+            description = "Values other than \"production\" adds a banner to each page.";
+            type = types.enum [ "development" "production" ];
+            default = "development";
+          };
+          network-key = mkOption {
+            description = ''
+              An absolute file path (which should be outside the Nix-store)
+              to a secret key to encrypt internal messages with. Use <code>srht-keygen network</code> to
+              generate this key. It must be consistent between all services and nodes.
+            '';
+            type = types.path;
+            apply = s: "<" + toString s;
+          };
+          owner-email = mkOption {
+            description = "Owner's email.";
+            type = types.str;
+            default = "contact@example.com";
+          };
+          owner-name = mkOption {
+            description = "Owner's name.";
+            type = types.str;
+            default = "John Doe";
+          };
+          site-blurb = mkOption {
+            description = "Blurb for your site.";
+            type = types.str;
+            default = "the hacker's forge";
+          };
+          site-info = mkOption {
+            description = "The top-level info page for your site.";
+            type = types.str;
+            default = "https://sourcehut.org";
+          };
+          service-key = mkOption {
+            description = ''
+              An absolute file path (which should be outside the Nix-store)
+              to a key used for encrypting session cookies. Use <code>srht-keygen service</code> to
+              generate the service key. This must be shared between each node of the same
+              service (e.g. git1.sr.ht and git2.sr.ht), but different services may use
+              different keys. If you configure all of your services with the same
+              config.ini, you may use the same service-key for all of them.
+            '';
+            type = types.path;
+            apply = s: "<" + toString s;
+          };
+          site-name = mkOption {
+            description = "The name of your network of sr.ht-based sites.";
+            type = types.str;
+            default = "sourcehut";
+          };
+          source-url = mkOption {
+            description = "The source code for your fork of sr.ht.";
+            type = types.str;
+            default = "https://git.sr.ht/~sircmpwn/srht";
+          };
+        };
+        options.mail = {
+          smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
+          smtp-port = mkOption {
+            description = "Outgoing SMTP port.";
+            type = with types; nullOr port;
+            default = null;
+          };
+          smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
+          smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
+          smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
+          error-to = mkOptionNullOrStr "Address receiving application exceptions";
+          error-from = mkOptionNullOrStr "Address sending application exceptions";
+          pgp-privkey = mkOptionNullOrStr ''
+            An absolute file path (which should be outside the Nix-store)
+            to an OpenPGP private key.
+
+            Your PGP key information (DO NOT mix up pub and priv here)
+            You must remove the password from your secret key, if present.
+            You can do this with <code>gpg --edit-key [key-id]</code>,
+            then use the <code>passwd</code> command and do not enter a new password.
+          '';
+          pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
+          pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
+        };
+        options.objects = {
+          s3-upstream = mkOption {
+            description = "Configure the S3-compatible object storage service.";
+            type = with types; nullOr str;
+            default = null;
+          };
+          s3-access-key = mkOption {
+            description = "Access key to the S3-compatible object storage service";
+            type = with types; nullOr str;
+            default = null;
+          };
+          s3-secret-key = mkOption {
+            description = ''
+              An absolute file path (which should be outside the Nix-store)
+              to the secret key of the S3-compatible object storage service.
+            '';
+            type = with types; nullOr path;
+            default = null;
+            apply = mapNullable (s: "<" + toString s);
+          };
+        };
+        options.webhooks = {
+          private-key = mkOption {
+            description = ''
+              An absolute file path (which should be outside the Nix-store)
+              to a base64-encoded Ed25519 key for signing webhook payloads.
+              This should be consistent for all *.sr.ht sites,
+              as this key will be used to verify signatures
+              from other sites in your network.
+              Use the <code>srht-keygen webhook</code> command to generate a key.
+            '';
+            type = types.path;
+            apply = s: "<" + toString s;
+          };
+        };
+
+        options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
+        };
+        options."dispatch.sr.ht::github" = {
+          oauth-client-id = mkOptionNullOrStr "OAuth client id.";
+          oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
+        };
+        options."dispatch.sr.ht::gitlab" = {
+          enabled = mkEnableOption "GitLab integration";
+          canonical-upstream = mkOption {
+            type = types.str;
+            description = "Canonical upstream.";
+            default = "gitlab.com";
+          };
+          repo-cache = mkOption {
+            type = types.str;
+            description = "Repository cache directory.";
+            default = "./repo-cache";
+          };
+          "gitlab.com" = mkOption {
+            type = with types; nullOr str;
+            description = "GitLab id and secret.";
+            default = null;
+            example = "GitLab:application id:secret";
+          };
+        };
+
+        options."builds.sr.ht" = commonServiceSettings "builds" // {
+          allow-free = mkEnableOption "nonpaying users to submit builds";
+          redis = mkOption {
+            description = "The Redis connection used for the Celery worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-buildsrht/redis.sock?virtual_host=2";
+          };
+          shell = mkOption {
+            description = ''
+              Scripts used to launch on SSH connection.
+              <literal>/usr/bin/master-shell</literal> on master,
+              <literal>/usr/bin/runner-shell</literal> on runner.
+              If master and worker are on the same system
+              set to <literal>/usr/bin/runner-shell</literal>.
+            '';
+            type = types.enum ["/usr/bin/master-shell" "/usr/bin/runner-shell"];
+            default = "/usr/bin/master-shell";
+          };
+        };
+        options."builds.sr.ht::worker" = {
+          bind-address = mkOption {
+            description = ''
+              HTTP bind address for serving local build information/monitoring.
+            '';
+            type = types.str;
+            default = "localhost:8080";
+          };
+          buildlogs = mkOption {
+            description = "Path to write build logs.";
+            type = types.str;
+            default = "/var/log/sourcehut/buildsrht-worker";
+          };
+          name = mkOption {
+            description = ''
+              Listening address and listening port
+              of the build runner (with HTTP port if not 80).
+            '';
+            type = types.str;
+            default = "localhost:5020";
+          };
+          timeout = mkOption {
+            description = ''
+              Max build duration.
+              See <link xlink:href="https://golang.org/pkg/time/#ParseDuration"/>.
+            '';
+            type = types.str;
+            default = "3m";
+          };
+        };
+
+        options."git.sr.ht" = commonServiceSettings "git" // {
+          outgoing-domain = mkOption {
+            description = "Outgoing domain.";
+            type = types.str;
+            default = "https://git.localhost.localdomain";
+          };
+          post-update-script = mkOption {
+            description = ''
+              A post-update script which is installed in every git repo.
+              This setting is propagated to newer and existing repositories.
+            '';
+            type = types.path;
+            default = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
+            defaultText = "\${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
+          };
+          repos = mkOption {
+            description = ''
+              Path to git repositories on disk.
+              If changing the default, you must ensure that
+              the gitsrht's user as read and write access to it.
+            '';
+            type = types.str;
+            default = "/var/lib/sourcehut/gitsrht/repos";
+          };
+          webhooks = mkOption {
+            description = "The Redis connection used for the webhooks worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-gitsrht/redis.sock?virtual_host=1";
+          };
+        };
+        options."git.sr.ht::api" = {
+          internal-ipnet = mkOption {
+            description = ''
+              Set of IP subnets which are permitted to utilize internal API
+              authentication. This should be limited to the subnets
+              from which your *.sr.ht services are running.
+              See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+            '';
+            type = with types; listOf str;
+            default = [ "127.0.0.0/8" "::1/128" ];
+          };
+        };
+
+        options."hg.sr.ht" = commonServiceSettings "hg" // {
+          changegroup-script = mkOption {
+            description = ''
+              A changegroup script which is installed in every mercurial repo.
+              This setting is propagated to newer and existing repositories.
+            '';
+            type = types.str;
+            default = "${cfg.python}/bin/hgsrht-hook-changegroup";
+            defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
+          };
+          repos = mkOption {
+            description = ''
+              Path to mercurial repositories on disk.
+              If changing the default, you must ensure that
+              the hgsrht's user as read and write access to it.
+            '';
+            type = types.str;
+            default = "/var/lib/sourcehut/hgsrht/repos";
+          };
+          srhtext = mkOptionNullOrStr ''
+            Path to the srht mercurial extension
+            (defaults to where the hgsrht code is)
+          '';
+          clone_bundle_threshold = mkOption {
+            description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
+            type = types.ints.unsigned;
+            default = 50;
+          };
+          hg_ssh = mkOption {
+            description = "Path to hg-ssh (if not in $PATH).";
+            type = types.str;
+            default = "${pkgs.mercurial}/bin/hg-ssh";
+            defaultText = "\${pkgs.mercurial}/bin/hg-ssh";
+          };
+          webhooks = mkOption {
+            description = "The Redis connection used for the webhooks worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-hgsrht/redis.sock?virtual_host=1";
+          };
+        };
+
+        options."hub.sr.ht" = commonServiceSettings "hub" // {
+        };
+
+        options."lists.sr.ht" = commonServiceSettings "lists" // {
+          allow-new-lists = mkEnableOption "Allow creation of new lists.";
+          notify-from = mkOption {
+            description = "Outgoing email for notifications generated by users.";
+            type = types.str;
+            default = "lists-notify@localhost.localdomain";
+          };
+          posting-domain = mkOption {
+            description = "Posting domain.";
+            type = types.str;
+            default = "lists.localhost.localdomain";
+          };
+          redis = mkOption {
+            description = "The Redis connection used for the Celery worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=2";
+          };
+          webhooks = mkOption {
+            description = "The Redis connection used for the webhooks worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=1";
+          };
+        };
+        options."lists.sr.ht::worker" = {
+          reject-mimetypes = mkOption {
+            description = ''
+              Comma-delimited list of Content-Types to reject. Messages with Content-Types
+              included in this list are rejected. Multipart messages are always supported,
+              and each part is checked against this list.
+
+              Uses fnmatch for wildcard expansion.
+            '';
+            type = with types; listOf str;
+            default = ["text/html"];
+          };
+          reject-url = mkOption {
+            description = "Reject URL.";
+            type = types.str;
+            default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
+          };
+          sock = mkOption {
+            description = ''
+              Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
+              Alternatively, specify IP:PORT and an SMTP server will be run instead.
+            '';
+            type = types.str;
+            default = "/tmp/lists.sr.ht-lmtp.sock";
+          };
+          sock-group = mkOption {
+            description = ''
+              The lmtp daemon will make the unix socket group-read/write
+              for users in this group.
+            '';
+            type = types.str;
+            default = "postfix";
+          };
+        };
+
+        options."man.sr.ht" = commonServiceSettings "man" // {
+        };
+
+        options."meta.sr.ht" =
+          removeAttrs (commonServiceSettings "meta")
+            ["oauth-client-id" "oauth-client-secret"] // {
+          api-origin = mkOption {
+            description = "Origin URL for API, 100 more than web.";
+            type = types.str;
+            default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
+            defaultText = ''http://<xref linkend="opt-services.sourcehut.listenAddress"/>:''${toString (<xref linkend="opt-services.sourcehut.meta.port"/> + 100)}'';
+          };
+          webhooks = mkOption {
+            description = "The Redis connection used for the webhooks worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-metasrht/redis.sock?virtual_host=1";
+          };
+          welcome-emails = mkEnableOption "sending stock sourcehut welcome emails after signup";
+        };
+        options."meta.sr.ht::api" = {
+          internal-ipnet = mkOption {
+            description = ''
+              Set of IP subnets which are permitted to utilize internal API
+              authentication. This should be limited to the subnets
+              from which your *.sr.ht services are running.
+              See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+            '';
+            type = with types; listOf str;
+            default = [ "127.0.0.0/8" "::1/128" ];
+          };
+        };
+        options."meta.sr.ht::aliases" = mkOption {
+          description = "Aliases for the client IDs of commonly used OAuth clients.";
+          type = with types; attrsOf int;
+          default = {};
+          example = { "git.sr.ht" = 12345; };
+        };
+        options."meta.sr.ht::billing" = {
+          enabled = mkEnableOption "the billing system";
+          stripe-public-key = mkOptionNullOrStr "Public key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys";
+          stripe-secret-key = mkOptionNullOrStr ''
+            An absolute file path (which should be outside the Nix-store)
+            to a secret key for Stripe. Get your keys at https://dashboard.stripe.com/account/apikeys
+          '' // {
+            apply = mapNullable (s: "<" + toString s);
+          };
+        };
+        options."meta.sr.ht::settings" = {
+          registration = mkEnableOption "public registration";
+          onboarding-redirect = mkOption {
+            description = "Where to redirect new users upon registration.";
+            type = types.str;
+            default = "https://meta.localhost.localdomain";
+          };
+          user-invites = mkOption {
+            description = ''
+              How many invites each user is issued upon registration
+              (only applicable if open registration is disabled).
+            '';
+            type = types.ints.unsigned;
+            default = 5;
+          };
+        };
+
+        options."pages.sr.ht" = commonServiceSettings "pages" // {
+          gemini-certs = mkOption {
+            description = ''
+              An absolute file path (which should be outside the Nix-store)
+              to Gemini certificates.
+            '';
+            type = with types; nullOr path;
+            default = null;
+          };
+          max-site-size = mkOption {
+            description = "Maximum size of any given site (post-gunzip), in MiB.";
+            type = types.int;
+            default = 1024;
+          };
+          user-domain = mkOption {
+            description = ''
+              Configures the user domain, if enabled.
+              All users are given &lt;username&gt;.this.domain.
+            '';
+            type = with types; nullOr str;
+            default = null;
+          };
+        };
+        options."pages.sr.ht::api" = {
+          internal-ipnet = mkOption {
+            description = ''
+              Set of IP subnets which are permitted to utilize internal API
+              authentication. This should be limited to the subnets
+              from which your *.sr.ht services are running.
+              See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+            '';
+            type = with types; listOf str;
+            default = [ "127.0.0.0/8" "::1/128" ];
+          };
+        };
+
+        options."paste.sr.ht" = commonServiceSettings "paste" // {
+        };
+
+        options."todo.sr.ht" = commonServiceSettings "todo" // {
+          notify-from = mkOption {
+            description = "Outgoing email for notifications generated by users.";
+            type = types.str;
+            default = "todo-notify@localhost.localdomain";
+          };
+          webhooks = mkOption {
+            description = "The Redis connection used for the webhooks worker.";
+            type = types.str;
+            default = "redis+socket:///run/redis-sourcehut-todosrht/redis.sock?virtual_host=1";
+          };
+        };
+        options."todo.sr.ht::mail" = {
+          posting-domain = mkOption {
+            description = "Posting domain.";
+            type = types.str;
+            default = "todo.localhost.localdomain";
+          };
+          sock = mkOption {
+            description = ''
+              Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
+              Alternatively, specify IP:PORT and an SMTP server will be run instead.
+            '';
+            type = types.str;
+            default = "/tmp/todo.sr.ht-lmtp.sock";
+          };
+          sock-group = mkOption {
+            description = ''
+              The lmtp daemon will make the unix socket group-read/write
+              for users in this group.
+            '';
+            type = types.str;
+            default = "postfix";
+          };
+        };
       };
       default = { };
       description = ''
         The configuration for the sourcehut network.
       '';
     };
+
+    builds = {
+      enableWorker = mkEnableOption ''
+        worker for builds.sr.ht
+
+        <warning><para>
+        For smaller deployments, job runners can be installed alongside the master server
+        but even if you only build your own software, integration with other services
+        may cause you to run untrusted builds
+        (e.g. automatic testing of patches via listssrht).
+        See <link xlink:href="https://man.sr.ht/builds.sr.ht/configuration.md#security-model"/>.
+        </para></warning>
+      '';
+
+      images = mkOption {
+        type = with types; attrsOf (attrsOf (attrsOf package));
+        default = { };
+        example = lib.literalExpression ''(let
+            # Pinning unstable to allow usage with flakes and limit rebuilds.
+            pkgs_unstable = builtins.fetchGit {
+                url = "https://github.com/NixOS/nixpkgs";
+                rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
+                ref = "nixos-unstable";
+            };
+            image_from_nixpkgs = (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
+              pkgs = (import pkgs_unstable {});
+            });
+          in
+          {
+            nixos.unstable.x86_64 = image_from_nixpkgs;
+          }
+        )'';
+        description = ''
+          Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
+        '';
+      };
+    };
+
+    git = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.git;
+        example = literalExpression "pkgs.gitFull";
+        description = ''
+          Git package for git.sr.ht. This can help silence collisions.
+        '';
+      };
+      fcgiwrap.preforkProcess = mkOption {
+        description = "Number of fcgiwrap processes to prefork.";
+        type = types.int;
+        default = 4;
+      };
+    };
+
+    hg = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mercurial;
+        description = ''
+          Mercurial package for hg.sr.ht. This can help silence collisions.
+        '';
+      };
+      cloneBundles = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
+        '';
+      };
+    };
+
+    lists = {
+      process = {
+        extraArgs = mkOption {
+          type = with types; listOf str;
+          default = [ "--loglevel DEBUG" "--pool eventlet" "--without-heartbeat" ];
+          description = "Extra arguments passed to the Celery responsible for processing mails.";
+        };
+        celeryConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = "Content of the <literal>celeryconfig.py</literal> used by the Celery of <literal>listssrht-process</literal>.";
+        };
+      };
+    };
   };
 
-  config = mkIf cfg.enable {
-    assertions =
-      [
+  config = mkIf cfg.enable (mkMerge [
+    {
+      environment.systemPackages = [ pkgs.sourcehut.coresrht ];
+
+      services.sourcehut.settings = {
+        "git.sr.ht".outgoing-domain = mkDefault "https://git.${domain}";
+        "lists.sr.ht".notify-from = mkDefault "lists-notify@${domain}";
+        "lists.sr.ht".posting-domain = mkDefault "lists.${domain}";
+        "meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${domain}";
+        "todo.sr.ht".notify-from = mkDefault "todo-notify@${domain}";
+        "todo.sr.ht::mail".posting-domain = mkDefault "todo.${domain}";
+      };
+    }
+    (mkIf cfg.postgresql.enable {
+      assertions = [
+        { assertion = postgresql.enable;
+          message = "postgresql must be enabled and configured";
+        }
+      ];
+    })
+    (mkIf cfg.postfix.enable {
+      assertions = [
+        { assertion = postfix.enable;
+          message = "postfix must be enabled and configured";
+        }
+      ];
+      # Needed for sharing the LMTP sockets with JoinsNamespaceOf=
+      systemd.services.postfix.serviceConfig.PrivateTmp = true;
+    })
+    (mkIf cfg.redis.enable {
+      services.redis.vmOverCommit = mkDefault true;
+    })
+    (mkIf cfg.nginx.enable {
+      assertions = [
+        { assertion = nginx.enable;
+          message = "nginx must be enabled and configured";
+        }
+      ];
+      # For proxyPass= in virtual-hosts for Sourcehut services.
+      services.nginx.recommendedProxySettings = mkDefault true;
+    })
+    (mkIf (cfg.builds.enable || cfg.git.enable || cfg.hg.enable) {
+      services.openssh = {
+        # Note that sshd will continue to honor AuthorizedKeysFile.
+        # Note that you may want automatically rotate
+        # or link to /dev/null the following log files:
+        # - /var/log/gitsrht-dispatch
+        # - /var/log/{build,git,hg}srht-keys
+        # - /var/log/{git,hg}srht-shell
+        # - /var/log/gitsrht-update-hook
+        authorizedKeysCommand = ''/etc/ssh/sourcehut/subdir/srht-dispatch "%u" "%h" "%t" "%k"'';
+        # srht-dispatch will setuid/setgid according to [git.sr.ht::dispatch]
+        authorizedKeysCommandUser = "root";
+        extraConfig = ''
+          PermitUserEnvironment SRHT_*
+        '';
+      };
+      environment.etc."ssh/sourcehut/config.ini".source =
+        settingsFormat.generate "sourcehut-dispatch-config.ini"
+          (filterAttrs (k: v: k == "git.sr.ht::dispatch")
+          cfg.settings);
+      environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
+        # sshd_config(5): The program must be owned by root, not writable by group or others
+        mode = "0755";
+        source = pkgs.writeShellScript "srht-dispatch" ''
+          set -e
+          cd /etc/ssh/sourcehut/subdir
+          ${cfg.python}/bin/gitsrht-dispatch "$@"
+        '';
+      };
+      systemd.services.sshd = {
+        #path = optional cfg.git.enable [ cfg.git.package ];
+        serviceConfig = {
+          BindReadOnlyPaths =
+            # Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
+            # for instance to get the user from the [git.sr.ht::dispatch] settings.
+            # *srht-keys needs to:
+            # - access a redis-server in [sr.ht] redis-host,
+            # - access the PostgreSQL server in [*.sr.ht] connection-string,
+            # - query metasrht-api (through the HTTP API).
+            # Using this has the side effect of creating empty files in /usr/bin/
+            optionals cfg.builds.enable [
+              "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
+                set -e
+                cd /run/sourcehut/buildsrht/subdir
+                set -x
+                exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
+              ''}:/usr/bin/buildsrht-keys"
+              "${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
+              "${pkgs.sourcehut.buildsrht}/bin/runner-shell:/usr/bin/runner-shell"
+            ] ++
+            optionals cfg.git.enable [
+              # /path/to/gitsrht-keys calls /path/to/gitsrht-shell,
+              # or [git.sr.ht] shell= if set.
+              "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
+                set -e
+                cd /run/sourcehut/gitsrht/subdir
+                set -x
+                exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
+              ''}:/usr/bin/gitsrht-keys"
+              "${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
+                set -e
+                cd /run/sourcehut/gitsrht/subdir
+                set -x
+                exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
+              ''}:/usr/bin/gitsrht-shell"
+              "${pkgs.writeShellScript "gitsrht-update-hook" ''
+                set -e
+                test -e "''${PWD%/*}"/config.ini ||
+                # Git hooks are run relative to their repository's directory,
+                # but gitsrht-update-hook looks up ../config.ini
+                ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
+                # hooks/post-update calls /usr/bin/gitsrht-update-hook as hooks/stage-3
+                # but this wrapper being a bash script, it overrides $0 with /usr/bin/gitsrht-update-hook
+                # hence this hack to put hooks/stage-3 back into gitsrht-update-hook's $0
+                if test "''${STAGE3:+set}"
+                then
+                  set -x
+                  exec -a hooks/stage-3 ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
+                else
+                  export STAGE3=set
+                  set -x
+                  exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
+                fi
+              ''}:/usr/bin/gitsrht-update-hook"
+            ] ++
+            optionals cfg.hg.enable [
+              # /path/to/hgsrht-keys calls /path/to/hgsrht-shell,
+              # or [hg.sr.ht] shell= if set.
+              "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
+                set -e
+                cd /run/sourcehut/hgsrht/subdir
+                set -x
+                exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
+              ''}:/usr/bin/hgsrht-keys"
+              "${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
+                set -e
+                cd /run/sourcehut/hgsrht/subdir
+                set -x
+                exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell "$@"
+              ''}:/usr/bin/hgsrht-shell"
+              # Mercurial's changegroup hooks are run relative to their repository's directory,
+              # but hgsrht-hook-changegroup looks up ./config.ini
+              "${pkgs.writeShellScript "hgsrht-hook-changegroup" ''
+                set -e
+                test -e "''$PWD"/config.ini ||
+                ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
+                set -x
+                exec -a "$0" ${cfg.python}/bin/hgsrht-hook-changegroup "$@"
+              ''}:/usr/bin/hgsrht-hook-changegroup"
+            ];
+        };
+      };
+    })
+  ]);
+
+  imports = [
+
+    (import ./service.nix "builds" {
+      inherit configIniOfService;
+      srvsrht = "buildsrht";
+      port = 5002;
+      # TODO: a celery worker on the master and worker are apparently needed
+      extraServices.buildsrht-worker = let
+        qemuPackage = pkgs.qemu_kvm;
+        serviceName = "buildsrht-worker";
+        statePath = "/var/lib/sourcehut/${serviceName}";
+        in mkIf cfg.builds.enableWorker {
+        path = [ pkgs.openssh pkgs.docker ];
+        preStart = ''
+          set -x
+          if test -z "$(docker images -q qemu:latest 2>/dev/null)" \
+          || test "$(cat ${statePath}/docker-image-qemu)" != "${qemuPackage.version}"
+          then
+            # Create and import qemu:latest image for docker
+            ${pkgs.dockerTools.streamLayeredImage {
+              name = "qemu";
+              tag = "latest";
+              contents = [ qemuPackage ];
+            }} | docker load
+            # Mark down current package version
+            echo '${qemuPackage.version}' >${statePath}/docker-image-qemu
+          fi
+        '';
+        serviceConfig = {
+          ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
+          BindPaths = [ cfg.settings."builds.sr.ht::worker".buildlogs ];
+          LogsDirectory = [ "sourcehut/${serviceName}" ];
+          RuntimeDirectory = [ "sourcehut/${serviceName}/subdir" ];
+          StateDirectory = [ "sourcehut/${serviceName}" ];
+          TimeoutStartSec = "1800s";
+          # builds.sr.ht-worker looks up ../config.ini
+          WorkingDirectory = "-"+"/run/sourcehut/${serviceName}/subdir";
+        };
+      };
+      extraConfig = let
+        image_dirs = flatten (
+          mapAttrsToList (distro: revs:
+            mapAttrsToList (rev: archs:
+              mapAttrsToList (arch: image:
+                pkgs.runCommand "buildsrht-images" { } ''
+                  mkdir -p $out/${distro}/${rev}/${arch}
+                  ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
+                ''
+              ) archs
+            ) revs
+          ) cfg.builds.images
+        );
+        image_dir_pre = pkgs.symlinkJoin {
+          name = "builds.sr.ht-worker-images-pre";
+          paths = image_dirs;
+            # FIXME: not working, apparently because ubuntu/latest is a broken link
+            # ++ [ "${pkgs.sourcehut.buildsrht}/lib/images" ];
+        };
+        image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
+          mkdir -p $out/images
+          cp -Lr ${image_dir_pre}/* $out/images
+        '';
+        in mkMerge [
         {
-          assertion = with cfgIni.webhooks; private-key != null && stringLength private-key == 44;
-          message = "The webhook's private key must be defined and of a 44 byte length.";
+          users.users.${cfg.builds.user}.shell = pkgs.bash;
+
+          virtualisation.docker.enable = true;
+
+          services.sourcehut.settings = mkMerge [
+            { # Note that git.sr.ht::dispatch is not a typo,
+              # gitsrht-dispatch always use this section
+              "git.sr.ht::dispatch"."/usr/bin/buildsrht-keys" =
+                mkDefault "${cfg.builds.user}:${cfg.builds.group}";
+            }
+            (mkIf cfg.builds.enableWorker {
+              "builds.sr.ht::worker".shell = "/usr/bin/runner-shell";
+              "builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
+              "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
+            })
+          ];
         }
+        (mkIf cfg.builds.enableWorker {
+          users.groups = {
+            docker.members = [ cfg.builds.user ];
+          };
+        })
+        (mkIf (cfg.builds.enableWorker && cfg.nginx.enable) {
+          # Allow nginx access to buildlogs
+          users.users.${nginx.user}.extraGroups = [ cfg.builds.group ];
+          systemd.services.nginx = {
+            serviceConfig.BindReadOnlyPaths = [ cfg.settings."builds.sr.ht::worker".buildlogs ];
+          };
+          services.nginx.virtualHosts."logs.${domain}" = mkMerge [ {
+            /* FIXME: is a listen needed?
+            listen = with builtins;
+              # FIXME: not compatible with IPv6
+              let address = split ":" cfg.settings."builds.sr.ht::worker".name; in
+              [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
+            */
+            locations."/logs/".alias = cfg.settings."builds.sr.ht::worker".buildlogs + "/";
+          } cfg.nginx.virtualHost ];
+        })
+      ];
+    })
+
+    (import ./service.nix "dispatch" {
+      inherit configIniOfService;
+      port = 5005;
+    })
 
+    (import ./service.nix "git" (let
+      baseService = {
+        path = [ cfg.git.package ];
+        serviceConfig.BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
+      };
+      in {
+      inherit configIniOfService;
+      mainService = mkMerge [ baseService {
+        serviceConfig.StateDirectory = [ "sourcehut/gitsrht" "sourcehut/gitsrht/repos" ];
+        preStart = mkIf (!versionAtLeast config.system.stateVersion "22.05") (mkBefore ''
+          # Fix Git hooks of repositories pre-dating https://github.com/NixOS/nixpkgs/pull/133984
+          (
+          set +f
+          shopt -s nullglob
+          for h in /var/lib/sourcehut/gitsrht/repos/~*/*/hooks/{pre-receive,update,post-update}
+          do ln -fnsv /usr/bin/gitsrht-update-hook "$h"; done
+          )
+        '');
+      } ];
+      port = 5001;
+      webhooks = true;
+      extraTimers.gitsrht-periodic = {
+        service = baseService;
+        timerConfig.OnCalendar = ["*:0/20"];
+      };
+      extraConfig = mkMerge [
         {
-          assertion = hasAttrByPath [ "meta.sr.ht" "origin" ] cfgIni && cfgIni."meta.sr.ht".origin != null;
-          message = "meta.sr.ht's origin must be defined.";
+          # https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
+          # Probably could use gitsrht-shell if output is restricted to just parameters...
+          users.users.${cfg.git.user}.shell = pkgs.bash;
+          services.sourcehut.settings = {
+            "git.sr.ht::dispatch"."/usr/bin/gitsrht-keys" =
+              mkDefault "${cfg.git.user}:${cfg.git.group}";
+          };
+          systemd.services.sshd = baseService;
         }
+        (mkIf cfg.nginx.enable {
+          services.nginx.virtualHosts."git.${domain}" = {
+            locations."/authorize" = {
+              proxyPass = "http://${cfg.listenAddress}:${toString cfg.git.port}";
+              extraConfig = ''
+                proxy_pass_request_body off;
+                proxy_set_header Content-Length "";
+                proxy_set_header X-Original-URI $request_uri;
+              '';
+            };
+            locations."~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$" = {
+              root = "/var/lib/sourcehut/gitsrht/repos";
+              fastcgiParams = {
+                GIT_HTTP_EXPORT_ALL = "";
+                GIT_PROJECT_ROOT = "$document_root";
+                PATH_INFO = "$uri";
+                SCRIPT_FILENAME = "${cfg.git.package}/bin/git-http-backend";
+              };
+              extraConfig = ''
+                auth_request /authorize;
+                fastcgi_read_timeout 500s;
+                fastcgi_pass unix:/run/gitsrht-fcgiwrap.sock;
+                gzip off;
+              '';
+            };
+          };
+          systemd.sockets.gitsrht-fcgiwrap = {
+            before = [ "nginx.service" ];
+            wantedBy = [ "sockets.target" "gitsrht.service" ];
+            # This path remains accessible to nginx.service, which has no RootDirectory=
+            socketConfig.ListenStream = "/run/gitsrht-fcgiwrap.sock";
+            socketConfig.SocketUser = nginx.user;
+            socketConfig.SocketMode = "600";
+          };
+        })
       ];
+      extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
+        serviceConfig = {
+          # Socket is passed by gitsrht-fcgiwrap.socket
+          ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${toString cfg.git.fcgiwrap.preforkProcess}";
+          # No need for config.ini
+          ExecStartPre = mkForce [];
+          User = null;
+          DynamicUser = true;
+          BindReadOnlyPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
+          IPAddressDeny = "any";
+          InaccessiblePaths = [ "-+/run/postgresql" "-+/run/redis-sourcehut" ];
+          PrivateNetwork = true;
+          RestrictAddressFamilies = mkForce [ "none" ];
+          SystemCallFilter = mkForce [
+            "@system-service"
+            "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@setuid"
+            # @timer is needed for alarm()
+          ];
+        };
+      };
+    }))
+
+    (import ./service.nix "hg" (let
+      baseService = {
+        path = [ cfg.hg.package ];
+        serviceConfig.BindPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/sourcehut/hgsrht/repos" ];
+      };
+      in {
+      inherit configIniOfService;
+      mainService = mkMerge [ baseService {
+        serviceConfig.StateDirectory = [ "sourcehut/hgsrht" "sourcehut/hgsrht/repos" ];
+      } ];
+      port = 5010;
+      webhooks = true;
+      extraTimers.hgsrht-periodic = {
+        service = baseService;
+        timerConfig.OnCalendar = ["*:0/20"];
+      };
+      extraTimers.hgsrht-clonebundles = mkIf cfg.hg.cloneBundles {
+        service = baseService;
+        timerConfig.OnCalendar = ["daily"];
+        timerConfig.AccuracySec = "1h";
+      };
+      extraConfig = mkMerge [
+        {
+          users.users.${cfg.hg.user}.shell = pkgs.bash;
+          services.sourcehut.settings = {
+            # Note that git.sr.ht::dispatch is not a typo,
+            # gitsrht-dispatch always uses this section.
+            "git.sr.ht::dispatch"."/usr/bin/hgsrht-keys" =
+              mkDefault "${cfg.hg.user}:${cfg.hg.group}";
+          };
+          systemd.services.sshd = baseService;
+        }
+        (mkIf cfg.nginx.enable {
+          # Allow nginx access to repositories
+          users.users.${nginx.user}.extraGroups = [ cfg.hg.group ];
+          services.nginx.virtualHosts."hg.${domain}" = {
+            locations."/authorize" = {
+              proxyPass = "http://${cfg.listenAddress}:${toString cfg.hg.port}";
+              extraConfig = ''
+                proxy_pass_request_body off;
+                proxy_set_header Content-Length "";
+                proxy_set_header X-Original-URI $request_uri;
+              '';
+            };
+            # Let clients reach pull bundles. We don't really need to lock this down even for
+            # private repos because the bundles are named after the revision hashes...
+            # so someone would need to know or guess a SHA value to download anything.
+            # TODO: proxyPass to an hg serve service?
+            locations."~ ^/[~^][a-z0-9_]+/[a-zA-Z0-9_.-]+/\\.hg/bundles/.*$" = {
+              root = "/var/lib/nginx/hgsrht/repos";
+              extraConfig = ''
+                auth_request /authorize;
+                gzip off;
+              '';
+            };
+          };
+          systemd.services.nginx = {
+            serviceConfig.BindReadOnlyPaths = [ "${cfg.settings."hg.sr.ht".repos}:/var/lib/nginx/hgsrht/repos" ];
+          };
+        })
+      ];
+    }))
+
+    (import ./service.nix "hub" {
+      inherit configIniOfService;
+      port = 5014;
+      extraConfig = {
+        services.nginx = mkIf cfg.nginx.enable {
+          virtualHosts."hub.${domain}" = mkMerge [ {
+            serverAliases = [ domain ];
+          } cfg.nginx.virtualHost ];
+        };
+      };
+    })
+
+    (import ./service.nix "lists" (let
+      srvsrht = "listssrht";
+      in {
+      inherit configIniOfService;
+      port = 5006;
+      webhooks = true;
+      # Receive the mail from Postfix and enqueue them into Redis and PostgreSQL
+      extraServices.listssrht-lmtp = {
+        wants = [ "postfix.service" ];
+        unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
+        serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
+        # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
+        serviceConfig.PrivateUsers = mkForce false;
+      };
+      # Dequeue the mails from Redis and dispatch them
+      extraServices.listssrht-process = {
+        serviceConfig = {
+          preStart = ''
+            cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" cfg.lists.process.celeryConfig} \
+               /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
+          '';
+          ExecStart = "${cfg.python}/bin/celery --app listssrht.process worker --hostname listssrht-process@%%h " + concatStringsSep " " cfg.lists.process.extraArgs;
+          # Avoid crashing: os.getloadavg()
+          ProcSubset = mkForce "all";
+        };
+      };
+      extraConfig = mkIf cfg.postfix.enable {
+        users.groups.${postfix.group}.members = [ cfg.lists.user ];
+        services.sourcehut.settings."lists.sr.ht::mail".sock-group = postfix.group;
+        services.postfix = {
+          destination = [ "lists.${domain}" ];
+          # FIXME: an accurate recipient list should be queried
+          # from the lists.sr.ht PostgreSQL database to avoid backscattering.
+          # But usernames are unfortunately not in that database but in meta.sr.ht.
+          # Note that two syntaxes are allowed:
+          # - ~username/list-name@lists.${domain}
+          # - u.username.list-name@lists.${domain}
+          localRecipients = [ "@lists.${domain}" ];
+          transport = ''
+            lists.${domain} lmtp:unix:${cfg.settings."lists.sr.ht::worker".sock}
+          '';
+        };
+      };
+    }))
+
+    (import ./service.nix "man" {
+      inherit configIniOfService;
+      port = 5004;
+    })
+
+    (import ./service.nix "meta" {
+      inherit configIniOfService;
+      port = 5000;
+      webhooks = true;
+      extraServices.metasrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "2s";
+        preStart = "set -x\n" + concatStringsSep "\n\n" (attrValues (mapAttrs (k: s:
+          let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht$" k;
+              srv = head srvMatch;
+          in
+          # Configure client(s) as "preauthorized"
+          optionalString (srvMatch != null && cfg.${srv}.enable && ((s.oauth-client-id or null) != null)) ''
+            # Configure ${srv}'s OAuth client as "preauthorized"
+            ${postgresql.package}/bin/psql '${cfg.settings."meta.sr.ht".connection-string}' \
+              -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${s.oauth-client-id}'"
+          ''
+          ) cfg.settings));
+        serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
+      };
+      extraTimers.metasrht-daily.timerConfig = {
+        OnCalendar = ["daily"];
+        AccuracySec = "1h";
+      };
+      extraConfig = mkMerge [
+        {
+          assertions = [
+            { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
+                          s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
+              message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
+            }
+          ];
+          environment.systemPackages = optional cfg.meta.enable
+            (pkgs.writeShellScriptBin "metasrht-manageuser" ''
+              set -eux
+              if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
+              then exec sudo -u '${cfg.meta.user}' "$0" "$@"
+              else
+                # In order to load config.ini
+                if cd /run/sourcehut/metasrht
+                then exec ${cfg.python}/bin/metasrht-manageuser "$@"
+                else cat <<EOF
+                  Please run: sudo systemctl start metasrht
+              EOF
+                  exit 1
+                fi
+              fi
+            '');
+        }
+        (mkIf cfg.nginx.enable {
+          services.nginx.virtualHosts."meta.${domain}" = {
+            locations."/query" = {
+              proxyPass = cfg.settings."meta.sr.ht".api-origin;
+              extraConfig = ''
+                if ($request_method = 'OPTIONS') {
+                  add_header 'Access-Control-Allow-Origin' '*';
+                  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+                  add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+                  add_header 'Access-Control-Max-Age' 1728000;
+                  add_header 'Content-Type' 'text/plain; charset=utf-8';
+                  add_header 'Content-Length' 0;
+                  return 204;
+                }
+
+                add_header 'Access-Control-Allow-Origin' '*';
+                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+                add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
+              '';
+            };
+          };
+        })
+      ];
+    })
+
+    (import ./service.nix "pages" {
+      inherit configIniOfService;
+      port = 5112;
+      mainService = let
+        srvsrht = "pagessrht";
+        version = pkgs.sourcehut.${srvsrht}.version;
+        stateDir = "/var/lib/sourcehut/${srvsrht}";
+        iniKey = "pages.sr.ht";
+        in {
+        preStart = mkBefore ''
+          set -x
+          # Use the /run/sourcehut/${srvsrht}/config.ini
+          # installed by a previous ExecStartPre= in baseService
+          cd /run/sourcehut/${srvsrht}
+
+          if test ! -e ${stateDir}/db; then
+            ${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f ${pkgs.sourcehut.pagessrht}/share/sql/schema.sql
+            echo ${version} >${stateDir}/db
+          fi
+
+          ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
+            # Just try all the migrations because they're not linked to the version
+            for sql in ${pkgs.sourcehut.pagessrht}/share/sql/migrations/*.sql; do
+              ${postgresql.package}/bin/psql '${cfg.settings.${iniKey}.connection-string}' -f "$sql" || true
+            done
+          ''}
+
+          # Disable webhook
+          touch ${stateDir}/webhook
+        '';
+        serviceConfig = {
+          ExecStart = mkForce "${pkgs.sourcehut.pagessrht}/bin/pages.sr.ht -b ${cfg.listenAddress}:${toString cfg.pages.port}";
+        };
+      };
+    })
+
+    (import ./service.nix "paste" {
+      inherit configIniOfService;
+      port = 5011;
+    })
+
+    (import ./service.nix "todo" {
+      inherit configIniOfService;
+      port = 5003;
+      webhooks = true;
+      extraServices.todosrht-lmtp = {
+        wants = [ "postfix.service" ];
+        unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
+        serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
+        # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
+        serviceConfig.PrivateUsers = mkForce false;
+      };
+      extraConfig = mkIf cfg.postfix.enable {
+        users.groups.${postfix.group}.members = [ cfg.todo.user ];
+        services.sourcehut.settings."todo.sr.ht::mail".sock-group = postfix.group;
+        services.postfix = {
+          destination = [ "todo.${domain}" ];
+          # FIXME: an accurate recipient list should be queried
+          # from the todo.sr.ht PostgreSQL database to avoid backscattering.
+          # But usernames are unfortunately not in that database but in meta.sr.ht.
+          # Note that two syntaxes are allowed:
+          # - ~username/tracker-name@todo.${domain}
+          # - u.username.tracker-name@todo.${domain}
+          localRecipients = [ "@todo.${domain}" ];
+          transport = ''
+            todo.${domain} lmtp:unix:${cfg.settings."todo.sr.ht::mail".sock}
+          '';
+        };
+      };
+    })
+
+    (mkRenamedOptionModule [ "services" "sourcehut" "originBase" ]
+                           [ "services" "sourcehut" "settings" "sr.ht" "global-domain" ])
+    (mkRenamedOptionModule [ "services" "sourcehut" "address" ]
+                           [ "services" "sourcehut" "listenAddress" ])
+
+  ];
 
-    virtualisation.docker.enable = true;
-    environment.etc."sr.ht/config.ini".source =
-      settingsFormat.generate "sourcehut-config.ini" (mapAttrsRecursive
-        (
-          path: v: if v == null then "" else v
-        )
-        cfg.settings);
-
-    environment.systemPackages = [ pkgs.sourcehut.coresrht ];
-
-    # PostgreSQL server
-    services.postgresql.enable = mkOverride 999 true;
-    # Mail server
-    services.postfix.enable = mkOverride 999 true;
-    # Cron daemon
-    services.cron.enable = mkOverride 999 true;
-    # Redis server
-    services.redis.enable = mkOverride 999 true;
-    services.redis.bind = mkOverride 999 "127.0.0.1";
-
-    services.sourcehut.settings = {
-      # The name of your network of sr.ht-based sites
-      "sr.ht".site-name = mkDefault "sourcehut";
-      # The top-level info page for your site
-      "sr.ht".site-info = mkDefault "https://sourcehut.org";
-      # {{ site-name }}, {{ site-blurb }}
-      "sr.ht".site-blurb = mkDefault "the hacker's forge";
-      # If this != production, we add a banner to each page
-      "sr.ht".environment = mkDefault "development";
-      # Contact information for the site owners
-      "sr.ht".owner-name = mkDefault "Drew DeVault";
-      "sr.ht".owner-email = mkDefault "sir@cmpwn.com";
-      # The source code for your fork of sr.ht
-      "sr.ht".source-url = mkDefault "https://git.sr.ht/~sircmpwn/srht";
-      # A secret key to encrypt session cookies with
-      "sr.ht".secret-key = mkDefault null;
-      "sr.ht".global-domain = mkDefault null;
-
-      # Outgoing SMTP settings
-      mail.smtp-host = mkDefault null;
-      mail.smtp-port = mkDefault null;
-      mail.smtp-user = mkDefault null;
-      mail.smtp-password = mkDefault null;
-      mail.smtp-from = mkDefault null;
-      # Application exceptions are emailed to this address
-      mail.error-to = mkDefault null;
-      mail.error-from = mkDefault null;
-      # Your PGP key information (DO NOT mix up pub and priv here)
-      # You must remove the password from your secret key, if present.
-      # You can do this with gpg --edit-key [key-id], then use the passwd
-      # command and do not enter a new password.
-      mail.pgp-privkey = mkDefault null;
-      mail.pgp-pubkey = mkDefault null;
-      mail.pgp-key-id = mkDefault null;
-
-      # base64-encoded Ed25519 key for signing webhook payloads. This should be
-      # consistent for all *.sr.ht sites, as we'll use this key to verify signatures
-      # from other sites in your network.
-      #
-      # Use the srht-webhook-keygen command to generate a key.
-      webhooks.private-key = mkDefault null;
-    };
-  };
   meta.doc = ./sourcehut.xml;
-  meta.maintainers = with maintainers; [ tomberek ];
+  meta.maintainers = with maintainers; [ julm tomberek ];
 }
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/dispatch.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/dispatch.nix
index a9db17bebe8e..292a51d3e1c5 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/dispatch.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/dispatch.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.dispatch;
   iniKey = "dispatch.sr.ht";
@@ -38,6 +39,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/dispatchsrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/dispatchsrht"'';
       description = ''
         State path for dispatch.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/git.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/git.nix
index 2653d77876dc..5ce16df8cd87 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/git.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/git.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   scfg = cfg.git;
   iniKey = "git.sr.ht";
 
@@ -41,6 +42,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/gitsrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/gitsrht"'';
       description = ''
         State path for git.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/hg.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/hg.nix
index 5cd36bb04550..6ba1df8b6ddb 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/hg.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/hg.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   scfg = cfg.hg;
   iniKey = "hg.sr.ht";
 
@@ -40,6 +41,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/hgsrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/hgsrht"'';
       description = ''
         State path for hg.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/hub.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/hub.nix
index be3ea21011c7..7d137a765056 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/hub.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/hub.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.hub;
   iniKey = "hub.sr.ht";
@@ -38,6 +39,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/hubsrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/hubsrht"'';
       description = ''
         State path for hub.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/lists.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/lists.nix
index 7b1fe9fd4630..76f155caa05b 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/lists.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/lists.nix
@@ -1,11 +1,12 @@
 # Email setup is fairly involved, useful references:
 # https://drewdevault.com/2018/08/05/Local-mail-server.html
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.lists;
   iniKey = "lists.sr.ht";
@@ -42,6 +43,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/listssrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/listssrht"'';
       description = ''
         State path for lists.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/man.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/man.nix
index 7693396d187c..8ca271c32ee3 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/man.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/man.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.man;
   iniKey = "man.sr.ht";
@@ -38,6 +39,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/mansrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/mansrht"'';
       description = ''
         State path for man.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/meta.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/meta.nix
index 56127a824eb4..33e4f2332b53 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/meta.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/meta.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.meta;
   iniKey = "meta.sr.ht";
@@ -39,6 +40,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/metasrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/metasrht"'';
       description = ''
         State path for meta.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/paste.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/paste.nix
index b2d5151969ea..b481ebaf8917 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/paste.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/paste.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.paste;
   iniKey = "paste.sr.ht";
@@ -39,6 +40,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/pastesrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/pastesrht"'';
       description = ''
         State path for pastesrht.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix
index 65b4ad020f9a..f1706ad0a6a8 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/service.nix
@@ -1,66 +1,375 @@
-{ config, pkgs, lib }:
-serviceCfg: serviceDrv: iniKey: attrs:
+srv:
+{ configIniOfService
+, srvsrht ? "${srv}srht" # Because "buildsrht" does not follow that pattern (missing an "s").
+, iniKey ? "${srv}.sr.ht"
+, webhooks ? false
+, extraTimers ? {}
+, mainService ? {}
+, extraServices ? {}
+, extraConfig ? {}
+, port
+}:
+{ config, lib, pkgs, ... }:
+
+with lib;
 let
+  inherit (config.services) postgresql;
+  redis = config.services.redis.servers."sourcehut-${srvsrht}";
+  inherit (config.users) users;
   cfg = config.services.sourcehut;
-  cfgIni = cfg.settings."${iniKey}";
-  pgSuperUser = config.services.postgresql.superUser;
-
-  setupDB = pkgs.writeScript "${serviceDrv.pname}-gen-db" ''
-    #! ${cfg.python}/bin/python
-    from ${serviceDrv.pname}.app import db
-    db.create()
-  '';
+  configIni = configIniOfService srv;
+  srvCfg = cfg.${srv};
+  baseService = serviceName: { allowStripe ? false }: extraService: let
+    runDir = "/run/sourcehut/${serviceName}";
+    rootDir = "/run/sourcehut/chroots/${serviceName}";
+    in
+    mkMerge [ extraService {
+    after = [ "network.target" ] ++
+      optional cfg.postgresql.enable "postgresql.service" ++
+      optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
+    requires =
+      optional cfg.postgresql.enable "postgresql.service" ++
+      optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
+    path = [ pkgs.gawk ];
+    environment.HOME = runDir;
+    serviceConfig = {
+      User = mkDefault srvCfg.user;
+      Group = mkDefault srvCfg.group;
+      RuntimeDirectory = [
+        "sourcehut/${serviceName}"
+        # Used by *srht-keys which reads ../config.ini
+        "sourcehut/${serviceName}/subdir"
+        "sourcehut/chroots/${serviceName}"
+      ];
+      RuntimeDirectoryMode = "2750";
+      # No need for the chroot path once inside the chroot
+      InaccessiblePaths = [ "-+${rootDir}" ];
+      # g+rx is for group members (eg. fcgiwrap or nginx)
+      # to read Git/Mercurial repositories, buildlogs, etc.
+      # o+x is for intermediate directories created by BindPaths= and like,
+      # as they're owned by root:root.
+      UMask = "0026";
+      RootDirectory = rootDir;
+      RootDirectoryStartOnly = true;
+      PrivateTmp = true;
+      MountAPIVFS = true;
+      # config.ini is looked up in there, before /etc/srht/config.ini
+      # Note that it fails to be set in ExecStartPre=
+      WorkingDirectory = mkDefault ("-"+runDir);
+      BindReadOnlyPaths = [
+        builtins.storeDir
+        "/etc"
+        "/run/booted-system"
+        "/run/current-system"
+        "/run/systemd"
+        ] ++
+        optional cfg.postgresql.enable "/run/postgresql" ++
+        optional cfg.redis.enable "/run/redis-sourcehut-${srvsrht}";
+      # LoadCredential= are unfortunately not available in ExecStartPre=
+      # Hence this one is run as root (the +) with RootDirectoryStartOnly=
+      # to reach credentials wherever they are.
+      # Note that each systemd service gets its own ${runDir}/config.ini file.
+      ExecStartPre = mkBefore [("+"+pkgs.writeShellScript "${serviceName}-credentials" ''
+        set -x
+        # Replace values begining with a '<' by the content of the file whose name is after.
+        gawk '{ if (match($0,/^([^=]+=)<(.+)/,m)) { getline f < m[2]; print m[1] f } else print $0 }' ${configIni} |
+        ${optionalString (!allowStripe) "gawk '!/^stripe-secret-key=/' |"}
+        install -o ${srvCfg.user} -g root -m 400 /dev/stdin ${runDir}/config.ini
+      '')];
+      # The following options are only for optimizing:
+      # systemd-analyze security
+      AmbientCapabilities = "";
+      CapabilityBoundingSet = "";
+      # ProtectClock= adds DeviceAllow=char-rtc r
+      DeviceAllow = "";
+      LockPersonality = true;
+      MemoryDenyWriteExecute = true;
+      NoNewPrivileges = true;
+      PrivateDevices = true;
+      PrivateMounts = true;
+      PrivateNetwork = mkDefault false;
+      PrivateUsers = true;
+      ProcSubset = "pid";
+      ProtectClock = true;
+      ProtectControlGroups = true;
+      ProtectHome = true;
+      ProtectHostname = true;
+      ProtectKernelLogs = true;
+      ProtectKernelModules = true;
+      ProtectKernelTunables = true;
+      ProtectProc = "invisible";
+      ProtectSystem = "strict";
+      RemoveIPC = true;
+      RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+      RestrictNamespaces = true;
+      RestrictRealtime = true;
+      RestrictSUIDSGID = true;
+      #SocketBindAllow = [ "tcp:${toString srvCfg.port}" "tcp:${toString srvCfg.prometheusPort}" ];
+      #SocketBindDeny = "any";
+      SystemCallFilter = [
+        "@system-service"
+        "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@timer"
+        "@chown" "@setuid"
+      ];
+      SystemCallArchitectures = "native";
+    };
+  } ];
 in
-with serviceCfg; with lib; recursiveUpdate
 {
-  environment.HOME = statePath;
-  path = [ config.services.postgresql.package ] ++ (attrs.path or [ ]);
-  restartTriggers = [ config.environment.etc."sr.ht/config.ini".source ];
-  serviceConfig = {
-    Type = "simple";
-    User = user;
-    Group = user;
-    Restart = "always";
-    WorkingDirectory = statePath;
-  } // (if (cfg.statePath == "/var/lib/sourcehut/${serviceDrv.pname}") then {
-          StateDirectory = [ "sourcehut/${serviceDrv.pname}" ];
-        } else {})
-  ;
-
-  preStart = ''
-    if ! test -e ${statePath}/db; then
-      # Setup the initial database
-      ${setupDB}
-
-      # Set the initial state of the database for future database upgrades
-      if test -e ${cfg.python}/bin/${serviceDrv.pname}-migrate; then
-        # Run alembic stamp head once to tell alembic the schema is up-to-date
-        ${cfg.python}/bin/${serviceDrv.pname}-migrate stamp head
-      fi
-
-      printf "%s" "${serviceDrv.version}" > ${statePath}/db
-    fi
-
-    # Update copy of each users' profile to the latest
-    # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
-    if ! test -e ${statePath}/webhook; then
-      # Update ${iniKey}'s users' profile copy to the latest
-      ${cfg.python}/bin/srht-update-profiles ${iniKey}
-
-      touch ${statePath}/webhook
-    fi
-
-    ${optionalString (builtins.hasAttr "migrate-on-upgrade" cfgIni && cfgIni.migrate-on-upgrade == "yes") ''
-      if [ "$(cat ${statePath}/db)" != "${serviceDrv.version}" ]; then
-        # Manage schema migrations using alembic
-        ${cfg.python}/bin/${serviceDrv.pname}-migrate -a upgrade head
-
-        # Mark down current package version
-        printf "%s" "${serviceDrv.version}" > ${statePath}/db
-      fi
-    ''}
-
-    ${attrs.preStart or ""}
-  '';
+  options.services.sourcehut.${srv} = {
+    enable = mkEnableOption "${srv} service";
+
+    user = mkOption {
+      type = types.str;
+      default = srvsrht;
+      description = ''
+        User for ${srv}.sr.ht.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = srvsrht;
+      description = ''
+        Group for ${srv}.sr.ht.
+        Membership grants access to the Git/Mercurial repositories by default,
+        but not to the config.ini file (where secrets are).
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = port;
+      description = ''
+        Port on which the "${srv}" backend should listen.
+      '';
+    };
+
+    redis = {
+      host = mkOption {
+        type = types.str;
+        default = "unix:/run/redis-sourcehut-${srvsrht}/redis.sock?db=0";
+        example = "redis://shared.wireguard:6379/0";
+        description = ''
+          The redis host URL. This is used for caching and temporary storage, and must
+          be shared between nodes (e.g. git1.sr.ht and git2.sr.ht), but need not be
+          shared between services. It may be shared between services, however, with no
+          ill effect, if this better suits your infrastructure.
+        '';
+      };
+    };
+
+    postgresql = {
+      database = mkOption {
+        type = types.str;
+        default = "${srv}.sr.ht";
+        description = ''
+          PostgreSQL database name for the ${srv}.sr.ht service,
+          used if <xref linkend="opt-services.sourcehut.postgresql.enable"/> is <literal>true</literal>.
+        '';
+      };
+    };
+
+    gunicorn = {
+      extraArgs = mkOption {
+        type = with types; listOf str;
+        default = ["--timeout 120" "--workers 1" "--log-level=info"];
+        description = "Extra arguments passed to Gunicorn.";
+      };
+    };
+  } // optionalAttrs webhooks {
+    webhooks = {
+      extraArgs = mkOption {
+        type = with types; listOf str;
+        default = ["--loglevel DEBUG" "--pool eventlet" "--without-heartbeat"];
+        description = "Extra arguments passed to the Celery responsible for webhooks.";
+      };
+      celeryConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Content of the <literal>celeryconfig.py</literal> used by the Celery responsible for webhooks.";
+      };
+    };
+  };
+
+  config = lib.mkIf (cfg.enable && srvCfg.enable) (mkMerge [ extraConfig {
+    users = {
+      users = {
+        "${srvCfg.user}" = {
+          isSystemUser = true;
+          group = mkDefault srvCfg.group;
+          description = mkDefault "sourcehut user for ${srv}.sr.ht";
+        };
+      };
+      groups = {
+        "${srvCfg.group}" = { };
+      } // optionalAttrs (cfg.postgresql.enable
+        && hasSuffix "0" (postgresql.settings.unix_socket_permissions or "")) {
+        "postgres".members = [ srvCfg.user ];
+      } // optionalAttrs (cfg.redis.enable
+        && hasSuffix "0" (redis.settings.unixsocketperm or "")) {
+        "redis-sourcehut-${srvsrht}".members = [ srvCfg.user ];
+      };
+    };
+
+    services.nginx = mkIf cfg.nginx.enable {
+      virtualHosts."${srv}.${cfg.settings."sr.ht".global-domain}" = mkMerge [ {
+        forceSSL = mkDefault true;
+        locations."/".proxyPass = "http://${cfg.listenAddress}:${toString srvCfg.port}";
+        locations."/static" = {
+          root = "${pkgs.sourcehut.${srvsrht}}/${pkgs.sourcehut.python.sitePackages}/${srvsrht}";
+          extraConfig = mkDefault ''
+            expires 30d;
+          '';
+        };
+      } cfg.nginx.virtualHost ];
+    };
+
+    services.postgresql = mkIf cfg.postgresql.enable {
+      authentication = ''
+        local ${srvCfg.postgresql.database} ${srvCfg.user} trust
+      '';
+      ensureDatabases = [ srvCfg.postgresql.database ];
+      ensureUsers = map (name: {
+          inherit name;
+          ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
+        }) [srvCfg.user];
+    };
+
+    services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
+      [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
+
+    services.sourcehut.settings = mkMerge [
+      {
+        "${srv}.sr.ht".origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
+      }
+
+      (mkIf cfg.postgresql.enable {
+        "${srv}.sr.ht".connection-string = mkDefault "postgresql:///${srvCfg.postgresql.database}?user=${srvCfg.user}&host=/run/postgresql";
+      })
+    ];
+
+    services.redis.servers."sourcehut-${srvsrht}" = mkIf cfg.redis.enable {
+      enable = true;
+      databases = 3;
+      syslog = true;
+      # TODO: set a more informed value
+      save = mkDefault [ [1800 10] [300 100] ];
+      settings = {
+        # TODO: set a more informed value
+        maxmemory = "128MB";
+        maxmemory-policy = "volatile-ttl";
+      };
+    };
+
+    systemd.services = mkMerge [
+      {
+        "${srvsrht}" = baseService srvsrht { allowStripe = srv == "meta"; } (mkMerge [
+        {
+          description = "sourcehut ${srv}.sr.ht website service";
+          before = optional cfg.nginx.enable "nginx.service";
+          wants = optional cfg.nginx.enable "nginx.service";
+          wantedBy = [ "multi-user.target" ];
+          path = optional cfg.postgresql.enable postgresql.package;
+          # Beware: change in credentials' content will not trigger restart.
+          restartTriggers = [ configIni ];
+          serviceConfig = {
+            Type = "simple";
+            Restart = mkDefault "always";
+            #RestartSec = mkDefault "2min";
+            StateDirectory = [ "sourcehut/${srvsrht}" ];
+            StateDirectoryMode = "2750";
+            ExecStart = "${cfg.python}/bin/gunicorn ${srvsrht}.app:app --name ${srvsrht} --bind ${cfg.listenAddress}:${toString srvCfg.port} " + concatStringsSep " " srvCfg.gunicorn.extraArgs;
+          };
+          preStart = let
+            version = pkgs.sourcehut.${srvsrht}.version;
+            stateDir = "/var/lib/sourcehut/${srvsrht}";
+            in mkBefore ''
+            set -x
+            # Use the /run/sourcehut/${srvsrht}/config.ini
+            # installed by a previous ExecStartPre= in baseService
+            cd /run/sourcehut/${srvsrht}
+
+            if test ! -e ${stateDir}/db; then
+              # Setup the initial database.
+              # Note that it stamps the alembic head afterward
+              ${cfg.python}/bin/${srvsrht}-initdb
+              echo ${version} >${stateDir}/db
+            fi
+
+            ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
+              if [ "$(cat ${stateDir}/db)" != "${version}" ]; then
+                # Manage schema migrations using alembic
+                ${cfg.python}/bin/${srvsrht}-migrate -a upgrade head
+                echo ${version} >${stateDir}/db
+              fi
+            ''}
+
+            # Update copy of each users' profile to the latest
+            # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
+            if test ! -e ${stateDir}/webhook; then
+              # Update ${iniKey}'s users' profile copy to the latest
+              ${cfg.python}/bin/srht-update-profiles ${iniKey}
+              touch ${stateDir}/webhook
+            fi
+          '';
+        } mainService ]);
+      }
+
+      (mkIf webhooks {
+        "${srvsrht}-webhooks" = baseService "${srvsrht}-webhooks" {}
+          {
+            description = "sourcehut ${srv}.sr.ht webhooks service";
+            after = [ "${srvsrht}.service" ];
+            wantedBy = [ "${srvsrht}.service" ];
+            partOf = [ "${srvsrht}.service" ];
+            preStart = ''
+              cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" srvCfg.webhooks.celeryConfig} \
+                 /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
+            '';
+            serviceConfig = {
+              Type = "simple";
+              Restart = "always";
+              ExecStart = "${cfg.python}/bin/celery --app ${srvsrht}.webhooks worker --hostname ${srvsrht}-webhooks@%%h " + concatStringsSep " " srvCfg.webhooks.extraArgs;
+              # Avoid crashing: os.getloadavg()
+              ProcSubset = mkForce "all";
+            };
+          };
+      })
+
+      (mapAttrs (timerName: timer: (baseService timerName {} (mkMerge [
+        {
+          description = "sourcehut ${timerName} service";
+          after = [ "network.target" "${srvsrht}.service" ];
+          serviceConfig = {
+            Type = "oneshot";
+            ExecStart = "${cfg.python}/bin/${timerName}";
+          };
+        }
+        (timer.service or {})
+      ]))) extraTimers)
+
+      (mapAttrs (serviceName: extraService: baseService serviceName {} (mkMerge [
+        {
+          description = "sourcehut ${serviceName} service";
+          # So that extraServices have the PostgreSQL database initialized.
+          after = [ "${srvsrht}.service" ];
+          wantedBy = [ "${srvsrht}.service" ];
+          partOf = [ "${srvsrht}.service" ];
+          serviceConfig = {
+            Type = "simple";
+            Restart = mkDefault "always";
+          };
+        }
+        extraService
+      ])) extraServices)
+    ];
+
+    systemd.timers = mapAttrs (timerName: timer:
+      {
+        description = "sourcehut timer for ${timerName}";
+        wantedBy = [ "timers.target" ];
+        inherit (timer) timerConfig;
+      }) extraTimers;
+  } ]);
 }
-  (builtins.removeAttrs attrs [ "path" "preStart" ])
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml b/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml
index ab9a8c6cb4be..41094f65a94d 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/sourcehut.xml
@@ -14,13 +14,12 @@
   <title>Basic usage</title>
   <para>
    Sourcehut is a Python and Go based set of applications.
-   <literal><link linkend="opt-services.sourcehut.enable">services.sourcehut</link></literal>
-   by default will use
+   This NixOS module also provides basic configuration integrating Sourcehut into locally running
    <literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>,
-   <literal><link linkend="opt-services.nginx.enable">services.redis</link></literal>,
-   <literal><link linkend="opt-services.nginx.enable">services.cron</link></literal>,
+   <literal><link linkend="opt-services.redis.servers">services.redis.servers.sourcehut</link></literal>,
+   <literal><link linkend="opt-services.postfix.enable">services.postfix</link></literal>
    and
-   <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>.
+   <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal> services.
   </para>
 
   <para>
@@ -42,18 +41,23 @@ in {
 
   services.sourcehut = {
     <link linkend="opt-services.sourcehut.enable">enable</link> = true;
-    <link linkend="opt-services.sourcehut.originBase">originBase</link> = fqdn;
-    <link linkend="opt-services.sourcehut.services">services</link> = [ "meta" "man" "git" ];
+    <link linkend="opt-services.sourcehut.git.enable">git.enable</link> = true;
+    <link linkend="opt-services.sourcehut.man.enable">man.enable</link> = true;
+    <link linkend="opt-services.sourcehut.meta.enable">meta.enable</link> = true;
+    <link linkend="opt-services.sourcehut.nginx.enable">nginx.enable</link> = true;
+    <link linkend="opt-services.sourcehut.postfix.enable">postfix.enable</link> = true;
+    <link linkend="opt-services.sourcehut.postgresql.enable">postgresql.enable</link> = true;
+    <link linkend="opt-services.sourcehut.redis.enable">redis.enable</link> = true;
     <link linkend="opt-services.sourcehut.settings">settings</link> = {
         "sr.ht" = {
           environment = "production";
           global-domain = fqdn;
           origin = "https://${fqdn}";
           # Produce keys with srht-keygen from <package>sourcehut.coresrht</package>.
-          network-key = "SECRET";
-          service-key = "SECRET";
+          network-key = "/run/keys/path/to/network-key";
+          service-key = "/run/keys/path/to/service-key";
         };
-        webhooks.private-key= "SECRET";
+        webhooks.private-key= "/run/keys/path/to/webhook-key";
     };
   };
 
diff --git a/nixpkgs/nixos/modules/services/misc/sourcehut/todo.nix b/nixpkgs/nixos/modules/services/misc/sourcehut/todo.nix
index aec773b06692..262fa48f59d4 100644
--- a/nixpkgs/nixos/modules/services/misc/sourcehut/todo.nix
+++ b/nixpkgs/nixos/modules/services/misc/sourcehut/todo.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.sourcehut;
+  opt = options.services.sourcehut;
   cfgIni = cfg.settings;
   scfg = cfg.todo;
   iniKey = "todo.sr.ht";
@@ -39,6 +40,7 @@ in
     statePath = mkOption {
       type = types.path;
       default = "${cfg.statePath}/todosrht";
+      defaultText = literalExpression ''"''${config.${opt.statePath}}/todosrht"'';
       description = ''
         State path for todo.sr.ht.
       '';
diff --git a/nixpkgs/nixos/modules/services/misc/subsonic.nix b/nixpkgs/nixos/modules/services/misc/subsonic.nix
index 98b85918ad18..2dda8970dd30 100644
--- a/nixpkgs/nixos/modules/services/misc/subsonic.nix
+++ b/nixpkgs/nixos/modules/services/misc/subsonic.nix
@@ -1,8 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
-let cfg = config.services.subsonic; in {
+let
+  cfg = config.services.subsonic;
+  opt = options.services.subsonic;
+in {
   options = {
     services.subsonic = {
       enable = mkEnableOption "Subsonic daemon";
@@ -97,7 +100,7 @@ let cfg = config.services.subsonic; in {
         description = ''
           List of paths to transcoder executables that should be accessible
           from Subsonic. Symlinks will be created to each executable inside
-          ${cfg.home}/transcoders.
+          ''${config.${opt.home}}/transcoders.
         '';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/misc/xmrig.nix b/nixpkgs/nixos/modules/services/misc/xmrig.nix
new file mode 100644
index 000000000000..c5c3803920c8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/xmrig.nix
@@ -0,0 +1,76 @@
+{ 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;
+        defaultText = literalExpression "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/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix b/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix
index b378d9f362fe..ff6d595e5a6e 100644
--- a/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix
+++ b/nixpkgs/nixos/modules/services/misc/zigbee2mqtt.nix
@@ -22,13 +22,9 @@ in
 
     package = mkOption {
       description = "Zigbee2mqtt package to use";
-      default = pkgs.zigbee2mqtt.override {
-        dataDir = cfg.dataDir;
-      };
+      default = pkgs.zigbee2mqtt;
       defaultText = literalExpression ''
-        pkgs.zigbee2mqtt {
-          dataDir = services.zigbee2mqtt.dataDir
-        }
+        pkgs.zigbee2mqtt
       '';
       type = types.package;
     };
@@ -41,7 +37,7 @@ in
 
     settings = mkOption {
       type = format.type;
-      default = {};
+      default = { };
       example = literalExpression ''
         {
           homeassistant = config.services.home-assistant.enable;
@@ -83,6 +79,7 @@ in
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/zigbee2mqtt";
         User = "zigbee2mqtt";
+        Group = "zigbee2mqtt";
         WorkingDirectory = cfg.dataDir;
         Restart = "on-failure";
 
diff --git a/nixpkgs/nixos/modules/services/misc/zoneminder.nix b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
index 378da7b87442..407742f72ad5 100644
--- a/nixpkgs/nixos/modules/services/misc/zoneminder.nix
+++ b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
@@ -171,7 +171,7 @@ in {
         example = "/storage/tank";
         description = ''
           ZoneMinder can generate quite a lot of data, so in case you don't want
-          to use the default ${home}, you can override the path here.
+          to use the default ${defaultDir}, you can override the path here.
         '';
       };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
index da051dbe4655..dfbf07efcaea 100644
--- a/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/monitoring/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
index ad0cf4735ad4..8d81737a3ef0 100644
--- a/nixpkgs/nixos/modules/services/monitoring/collectd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.collectd;
 
-  conf = pkgs.writeText "collectd.conf" ''
+  unvalidated_conf = pkgs.writeText "collectd-unvalidated.conf" ''
     BaseDir "${cfg.dataDir}"
     AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
     Hostname "${config.networking.hostName}"
@@ -30,6 +30,15 @@ let
     ${cfg.extraConfig}
   '';
 
+  conf = if cfg.validateConfig then
+    pkgs.runCommand "collectd.conf" {} ''
+      echo testing ${unvalidated_conf}
+      # collectd -t fails if BaseDir does not exist.
+      sed '1s/^BaseDir.*$/BaseDir "."/' ${unvalidated_conf} > collectd.conf
+      ${package}/bin/collectd -t -C collectd.conf
+      cp ${unvalidated_conf} $out
+    '' else unvalidated_conf;
+
   package =
     if cfg.buildMinimalPackage
     then minimalPackage
@@ -43,6 +52,16 @@ in {
   options.services.collectd = with types; {
     enable = mkEnableOption "collectd agent";
 
+    validateConfig = mkOption {
+      default = true;
+      description = ''
+        Validate the syntax of collectd configuration file at build time.
+        Disable this if you use the Include directive on files unavailable in
+        the build sandbox, or when cross-compiling.
+      '';
+      type = types.bool;
+    };
+
     package = mkOption {
       default = pkgs.collectd;
       defaultText = literalExpression "pkgs.collectd";
@@ -57,7 +76,7 @@ in {
       description = ''
         Build a minimal collectd package with only the configured `services.collectd.plugins`
       '';
-      type = types.bool;
+      type = bool;
     };
 
     user = mkOption {
@@ -98,7 +117,7 @@ in {
       description = ''
         Attribute set of plugin names to plugin config segments
       '';
-      type = types.attrsOf types.str;
+      type = attrsOf lines;
     };
 
     extraConfig = mkOption {
@@ -132,7 +151,12 @@ in {
     users.users = optionalAttrs (cfg.user == "collectd") {
       collectd = {
         isSystemUser = true;
+        group = "collectd";
       };
     };
+
+    users.groups = optionalAttrs (cfg.user == "collectd") {
+      collectd = {};
+    };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana.nix b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
index 9b21dc78b19d..81fca33f5fec 100644
--- a/nixpkgs/nixos/modules/services/monitoring/grafana.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
@@ -404,6 +404,7 @@ in {
       path = mkOption {
         description = "Database path.";
         default = "${cfg.dataDir}/data/grafana.db";
+        defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
         type = types.path;
       };
 
@@ -677,15 +678,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;
@@ -701,6 +700,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/nixpkgs/nixos/modules/services/monitoring/graphite.nix b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
index 4690a252c925..baa943302a00 100644
--- a/nixpkgs/nixos/modules/services/monitoring/graphite.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.graphite;
+  opt = options.services.graphite;
   writeTextOrNull = f: t: mapNullable (pkgs.writeTextDir f) t;
 
   dataDir = cfg.dataDir;
@@ -171,6 +172,13 @@ in {
             directories:
                 - ${dataDir}/whisper
         '';
+        defaultText = literalExpression ''
+          '''
+            whisper:
+              directories:
+                - ''${config.${opt.dataDir}}/whisper
+          '''
+        '';
         example = ''
           allowed_origins:
             - dashboard.example.com
@@ -312,18 +320,21 @@ in {
 
       seyrenUrl = mkOption {
         default = "http://localhost:${toString cfg.seyren.port}/";
+        defaultText = literalExpression ''"http://localhost:''${toString config.${opt.seyren.port}}/"'';
         description = "Host where seyren is accessible.";
         type = types.str;
       };
 
       graphiteUrl = mkOption {
         default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
+        defaultText = literalExpression ''"http://''${config.${opt.web.listenAddress}}:''${toString config.${opt.web.port}}"'';
         description = "Host where graphite service runs.";
         type = types.str;
       };
 
       mongoUrl = mkOption {
         default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
+        defaultText = literalExpression ''"mongodb://''${config.services.mongodb.bind_ip}:27017/seyren"'';
         description = "Mongodb connection string.";
         type = types.str;
       };
diff --git a/nixpkgs/nixos/modules/services/monitoring/nagios.nix b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
index 83020d52fc82..2c7f0ed19668 100644
--- a/nixpkgs/nixos/modules/services/monitoring/nagios.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
@@ -131,6 +131,7 @@ in
       validateConfig = mkOption {
         type = types.bool;
         default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform;
+        defaultText = literalExpression "pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform";
         description = "if true, the syntax of the nagios configuration file is checked at build time";
       };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix
index eeee04b4400c..ec71365ba3c1 100644
--- a/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/parsedmarc.nix
@@ -1,7 +1,8 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
   cfg = config.services.parsedmarc;
+  opt = options.services.parsedmarc;
   ini = pkgs.formats.ini {};
 in
 {
@@ -80,6 +81,9 @@ in
         datasource = lib.mkOption {
           type = lib.types.bool;
           default = cfg.provision.elasticsearch && config.services.grafana.enable;
+          defaultText = lib.literalExpression ''
+            config.${opt.provision.elasticsearch} && config.${options.services.grafana.enable}
+          '';
           apply = x: x && cfg.provision.elasticsearch;
           description = ''
             Whether the automatically provisioned Elasticsearch
@@ -93,6 +97,7 @@ in
         dashboard = lib.mkOption {
           type = lib.types.bool;
           default = config.services.grafana.enable;
+          defaultText = lib.literalExpression "config.services.grafana.enable";
           description = ''
             Whether the official parsedmarc grafana dashboard should
             be provisioned to the local grafana instance.
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
index d2b37cf688bf..f20b8dde1abd 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixpkgs/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 = {
@@ -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.
       '';
@@ -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/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
index 62e90232e114..d29d50706ef6 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -61,6 +61,7 @@ let
     "rtl_433"
     "script"
     "snmp"
+    "smartctl"
     "smokeping"
     "sql"
     "surfboard"
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
index 5b35bb29a301..55a61c4949ee 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
@@ -32,10 +32,10 @@ in
     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
+      ${pkgs.prometheus-fastly-exporter}/bin/fastly-exporter \
+        -listen http://${cfg.listenAddress}:${toString cfg.port}
         ${optionalString cfg.debug "-debug true"} \
-        ${optionalString cfg.configFile "-config-file ${cfg.configFile}"}
+        ${optionalString (cfg.configFile != null) "-config-file ${cfg.configFile}"}
     '';
   };
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 5ee8c346be1d..6f69f5919d1e 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -46,11 +46,11 @@ in
     serviceConfig = {
       ExecStart = ''
         ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
-          --nginx.scrape-uri '${cfg.scrapeUri}' \
-          --nginx.ssl-verify ${toString cfg.sslVerify} \
-          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-          --web.telemetry-path ${cfg.telemetryPath} \
-          --prometheus.const-labels ${concatStringsSep "," cfg.constLabels} \
+          --nginx.scrape-uri='${cfg.scrapeUri}' \
+          --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} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index baac21b050f5..5e5fc7cd5524 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -35,10 +35,15 @@ in
           ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
       '';
-      # The systemd collector needs AF_UNIX
-      RestrictAddressFamilies = lib.optional (lib.any (x: x == "systemd") cfg.enabledCollectors) "AF_UNIX";
+      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 = lib.any (x: x == "timex") cfg.disabledCollectors;
+      ProtectClock = any (collector: collector == "timex") cfg.disabledCollectors;
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
index f57589a59c7b..4d3c1fa267e5 100644
--- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
@@ -76,6 +76,9 @@ in
   serviceOpts = {
     serviceConfig = {
       DynamicUser = false;
+      # By default, each prometheus exporter only gets AF_INET & AF_INET6,
+      # but AF_UNIX is needed to read from the `showq`-socket.
+      RestrictAddressFamilies = [ "AF_UNIX" ];
       ExecStart = ''
         ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
new file mode 100644
index 000000000000..b6416b93e69c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.smartctl;
+  format = pkgs.formats.yaml {};
+  configFile = format.generate "smartctl-exporter.yml" {
+    smartctl_exporter = {
+      bind_to = "${cfg.listenAddress}:${toString cfg.port}";
+      url_path = "/metrics";
+      smartctl_location = "${pkgs.smartmontools}/bin/smartctl";
+      collect_not_more_than_period = cfg.maxInterval;
+      devices = cfg.devices;
+    };
+  };
+in {
+  port = 9633;
+
+  extraOpts = {
+    devices = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = literalExpression ''
+        [ "/dev/sda", "/dev/nvme0n1" ];
+      '';
+      description = ''
+        Paths to disks that will be monitored.
+      '';
+    };
+    maxInterval = mkOption {
+      type = types.str;
+      default = "60s";
+      example = "2m";
+      description = ''
+        Interval that limits how often a disk can be queried.
+      '';
+    };
+  };
+
+  serviceOpts = {
+    serviceConfig = {
+      AmbientCapabilities = [
+        "CAP_SYS_ADMIN"
+      ];
+      CapabilityBoundingSet = [
+        "CAP_SYS_ADMIN"
+      ];
+      DevicePolicy = "closed";
+      DeviceAllow = lib.mkForce cfg.devices;
+      ExecStart = ''
+        ${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter -config ${configFile}
+      '';
+      PrivateDevices = lib.mkForce false;
+      ProtectProc = "invisible";
+      ProcSubset = "pid";
+      SupplementaryGroups = [ "disk" ];
+      SystemCallFilter = [
+        "@system-service"
+        "~@privileged @resources"
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/smartd.nix b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
index 3ea254371142..6d39cc3e4e6b 100644
--- a/nixpkgs/nixos/modules/services/monitoring/smartd.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -8,6 +8,7 @@ let
        + optionalString (config.networking.domain != null) ".${config.networking.domain}";
 
   cfg = config.services.smartd;
+  opt = options.services.smartd;
 
   nm = cfg.notifications.mail;
   nw = cfg.notifications.wall;
@@ -125,6 +126,7 @@ in
         mail = {
           enable = mkOption {
             default = config.services.mail.sendmailSetuidWrapper != null;
+            defaultText = literalExpression "config.services.mail.sendmailSetuidWrapper != null";
             type = types.bool;
             description = "Whenever to send e-mail notifications.";
           };
@@ -169,12 +171,14 @@ in
         x11 = {
           enable = mkOption {
             default = config.services.xserver.enable;
+            defaultText = literalExpression "config.services.xserver.enable";
             type = types.bool;
             description = "Whenever to send X11 xmessage notifications.";
           };
 
           display = mkOption {
             default = ":${toString config.services.xserver.display}";
+            defaultText = literalExpression ''":''${toString config.services.xserver.display}"'';
             type = types.str;
             description = "DISPLAY to send X11 notifications to.";
           };
@@ -208,6 +212,7 @@ in
 
         autodetected = mkOption {
           default = cfg.defaults.monitored;
+          defaultText = literalExpression "config.${opt.defaults.monitored}";
           type = types.separatedString " ";
           description = ''
             Like <option>services.smartd.defaults.monitored</option>, but for the
diff --git a/nixpkgs/nixos/modules/services/monitoring/thanos.nix b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
index da626788d827..9e93d8dbb0ef 100644
--- a/nixpkgs/nixos/modules/services/monitoring/thanos.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
@@ -83,6 +83,9 @@ let
   mkArgumentsOption = cmd: mkOption {
     type = types.listOf types.str;
     default = argumentsOf cmd;
+    defaultText = literalDocBook ''
+      calculated from <literal>config.services.thanos.${cmd}</literal>
+    '';
     description = ''
       Arguments to the <literal>thanos ${cmd}</literal> command.
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix b/nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix
index 81a7b408bcc4..cca4a0e72071 100644
--- a/nixpkgs/nixos/modules/services/monitoring/unifi-poller.nix
+++ b/nixpkgs/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 {
@@ -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 {
@@ -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/nixpkgs/nixos/modules/services/monitoring/uptime.nix b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
index 245badc3e44f..79b86be6cc71 100644
--- a/nixpkgs/nixos/modules/services/monitoring/uptime.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
@@ -1,8 +1,9 @@
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 let
-  inherit (lib) mkOption mkEnableOption mkIf mkMerge types optional;
+  inherit (lib) literalExpression mkOption mkEnableOption mkIf mkMerge types optional;
 
   cfg = config.services.uptime;
+  opt = options.services.uptime;
 
   configDir = pkgs.runCommand "config" { preferLocalBuild = true; }
   (if cfg.configFile != null then ''
@@ -52,7 +53,10 @@ in {
 
     enableWebService = mkEnableOption "the uptime monitoring program web service";
 
-    enableSeparateMonitoringService = mkEnableOption "the uptime monitoring service" // { default = cfg.enableWebService; };
+    enableSeparateMonitoringService = mkEnableOption "the uptime monitoring service" // {
+      default = cfg.enableWebService;
+      defaultText = literalExpression "config.${opt.enableWebService}";
+    };
 
     nodeEnv = mkOption {
       description = "The node environment to run in (development, production, etc.)";
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
index b5009f47f175..0ebd7bcff834 100644
--- a/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -1,7 +1,8 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
   cfg = config.services.zabbixProxy;
+  opt = options.services.zabbixProxy;
   pgsql = config.services.postgresql;
   mysql = config.services.mysql;
 
@@ -103,6 +104,11 @@ in
         port = mkOption {
           type = types.int;
           default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
+          defaultText = literalExpression ''
+            if config.${opt.database.type} == "mysql"
+            then config.${options.services.mysql.port}
+            else config.${options.services.postgresql.port}
+          '';
           description = "Database host port.";
         };
 
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
index 9b0fd9dbff13..9f960517a81b 100644
--- a/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
@@ -1,7 +1,8 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
   cfg = config.services.zabbixServer;
+  opt = options.services.zabbixServer;
   pgsql = config.services.postgresql;
   mysql = config.services.mysql;
 
@@ -95,6 +96,11 @@ in
         port = mkOption {
           type = types.int;
           default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
+          defaultText = literalExpression ''
+            if config.${opt.database.type} == "mysql"
+            then config.${options.services.mysql.port}
+            else config.${options.services.postgresql.port}
+          '';
           description = "Database host port.";
         };
 
@@ -250,7 +256,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/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix b/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
index 916e7eaaaa94..c730e0b34e90 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
@@ -47,19 +47,17 @@ let cfg = config.services.drbd; in
         options drbd usermode_helper=/run/current-system/sw/bin/drbdadm
       '';
 
-    environment.etc.drbd.conf =
+    environment.etc."drbd.conf" =
       { source = pkgs.writeText "drbd.conf" cfg.config; };
 
     systemd.services.drbd = {
       after = [ "systemd-udev.settle.service" "network.target" ];
       wants = [ "systemd-udev.settle.service" ];
       wantedBy = [ "multi-user.target" ];
-      script = ''
-        ${pkgs.drbd}/sbin/drbdadm up all
-      '';
-      serviceConfig.ExecStop = ''
-        ${pkgs.drbd}/sbin/drbdadm down all
-      '';
+      serviceConfig = {
+        ExecStart = "${pkgs.drbd}/sbin/drbdadm up all";
+        ExecStop = "${pkgs.drbd}/sbin/drbdadm down all";
+      };
     };
   };
 }
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
index bc8be05ca8cb..38be098de5d9 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -187,7 +187,7 @@ in
 
       wantedBy = [ "multi-user.target" ];
 
-      after = [ "syslog.target" "network.target" ];
+      after = [ "network.target" ];
 
       preStart = ''
         install -m 0755 -d /var/log/glusterfs
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
index 36b72ca48b2c..5482b2aaf88c 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
@@ -79,6 +79,11 @@ in
           if versionAtLeast config.system.stateVersion "17.09"
           then "/var/lib/ipfs"
           else "/var/lib/ipfs/.ipfs";
+        defaultText = literalExpression ''
+          if versionAtLeast config.system.stateVersion "17.09"
+          then "/var/lib/ipfs"
+          else "/var/lib/ipfs/.ipfs"
+        '';
         description = "The data dir for IPFS";
       };
 
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
index c1bf83be77b9..9c974335defa 100644
--- a/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -248,7 +248,7 @@ in {
     systemd.services = {
       openafs-server = {
         description = "OpenAFS server";
-        after = [ "syslog.target" "network.target" ];
+        after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
         restartIfChanged = false;
         unitConfig.ConditionPathExists = [
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix b/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix
new file mode 100644
index 000000000000..1c5c299cb673
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/webdav-server-rs.nix
@@ -0,0 +1,144 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.webdav-server-rs;
+  format = pkgs.formats.toml { };
+  settings = recursiveUpdate
+    {
+      server.uid = config.users.users."${cfg.user}".uid;
+      server.gid = config.users.groups."${cfg.group}".gid;
+    }
+    cfg.settings;
+in
+{
+  options = {
+    services.webdav-server-rs = {
+      enable = mkEnableOption "WebDAV server";
+
+      user = mkOption {
+        type = types.str;
+        default = "webdav";
+        description = "User to run under when setuid is not enabled.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "webdav";
+        description = "Group to run under when setuid is not enabled.";
+      };
+
+      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/miquels/webdav-server-rs/blob/master/webdav-server.toml">here</link>.
+        '';
+        example = literalExpression ''
+          {
+            server.listen = [ "0.0.0.0:4918" "[::]:4918" ];
+            accounts = {
+              auth-type = "htpasswd.default";
+              acct-type = "unix";
+            };
+            htpasswd.default = {
+              htpasswd = "/etc/htpasswd";
+            };
+            location = [
+              {
+                route = [ "/public/*path" ];
+                directory = "/srv/public";
+                handler = "filesystem";
+                methods = [ "webdav-ro" ];
+                autoindex = true;
+                auth = "false";
+              }
+              {
+                route = [ "/user/:user/*path" ];
+                directory = "~";
+                handler = "filesystem";
+                methods = [ "webdav-rw" ];
+                autoindex = true;
+                auth = "true";
+                setuid = true;
+              }
+            ];
+          }
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = format.generate "webdav-server.toml" settings;
+        defaultText = "Config file generated from services.webdav-server-rs.settings";
+        description = ''
+          Path to config file. If this option is set, it will override any
+          configuration done in services.webdav-server-rs.settings.
+        '';
+        example = "/etc/webdav-server.toml";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = hasAttr cfg.user config.users.users && config.users.users."${cfg.user}".uid != null;
+        message = "users.users.${cfg.user} and users.users.${cfg.user}.uid must be defined.";
+      }
+      {
+        assertion = hasAttr cfg.group config.users.groups && config.users.groups."${cfg.group}".gid != null;
+        message = "users.groups.${cfg.group} and users.groups.${cfg.group}.gid must be defined.";
+      }
+    ];
+
+    users.users = optionalAttrs (cfg.user == "webdav") {
+      webdav = {
+        description = "WebDAV user";
+        group = cfg.group;
+        uid = config.ids.uids.webdav;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "webdav") {
+      webdav.gid = config.ids.gids.webdav;
+    };
+
+    systemd.services.webdav-server-rs = {
+      description = "WebDAV server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.webdav-server-rs}/bin/webdav-server -c ${cfg.configFile}";
+
+        CapabilityBoundingSet = [
+          "CAP_SETUID"
+          "CAP_SETGID"
+        ];
+
+        NoExecPaths = [ "/" ];
+        ExecPaths = [ "/nix/store" ];
+
+        # This program actively detects if it is running in root user account
+        # when it starts and uses root privilege to switch process uid to
+        # respective unix user when a user logs in.  Maybe we can enable
+        # DynamicUser in the future when it's able to detect CAP_SETUID and
+        # CAP_SETGID capabilities.
+
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = true;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ pmy ];
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/webdav.nix b/nixpkgs/nixos/modules/services/network-filesystems/webdav.nix
new file mode 100644
index 000000000000..a810af40fd47
--- /dev/null
+++ b/nixpkgs/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";
+        group = cfg.group;
+        uid = config.ids.uids.webdav;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "webdav") {
+      webdav.gid = config.ids.gids.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; [ pmy ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/adguardhome.nix b/nixpkgs/nixos/modules/services/networking/adguardhome.nix
index 4388ef2b7e57..03f9b9f9bad4 100644
--- a/nixpkgs/nixos/modules/services/networking/adguardhome.nix
+++ b/nixpkgs/nixos/modules/services/networking/adguardhome.nix
@@ -56,7 +56,7 @@ in
   config = mkIf cfg.enable {
     systemd.services.adguardhome = {
       description = "AdGuard Home: Network-level blocker";
-      after = [ "syslog.target" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       unitConfig = {
         StartLimitIntervalSec = 5;
diff --git a/nixpkgs/nixos/modules/services/networking/amuled.nix b/nixpkgs/nixos/modules/services/networking/amuled.nix
index 39320643dd5e..e55ac7a6b18b 100644
--- a/nixpkgs/nixos/modules/services/networking/amuled.nix
+++ b/nixpkgs/nixos/modules/services/networking/amuled.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.amule;
+  opt = options.services.amule;
   user = if cfg.user != null then cfg.user else "amule";
 in
 
@@ -26,6 +27,9 @@ in
       dataDir = mkOption {
         type = types.str;
         default = "/home/${user}/";
+        defaultText = literalExpression ''
+          "/home/''${config.${opt.user}}/"
+        '';
         description = ''
           The directory holding configuration, incoming and temporary files.
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/antennas.nix b/nixpkgs/nixos/modules/services/networking/antennas.nix
new file mode 100644
index 000000000000..ef98af22f20f
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/bind.nix b/nixpkgs/nixos/modules/services/networking/bind.nix
index f2b2e4c4d5d4..e44f8d4cf302 100644
--- a/nixpkgs/nixos/modules/services/networking/bind.nix
+++ b/nixpkgs/nixos/modules/services/networking/bind.nix
@@ -144,6 +144,7 @@ in
 
       forwarders = mkOption {
         default = config.networking.nameservers;
+        defaultText = literalExpression "config.networking.nameservers";
         type = types.listOf types.str;
         description = "
           List of servers we should forward requests to.
diff --git a/nixpkgs/nixos/modules/services/networking/charybdis.nix b/nixpkgs/nixos/modules/services/networking/charybdis.nix
index 43829d36e417..ff09c0160cb6 100644
--- a/nixpkgs/nixos/modules/services/networking/charybdis.nix
+++ b/nixpkgs/nixos/modules/services/networking/charybdis.nix
@@ -85,14 +85,21 @@ in
         "d ${cfg.statedir} - ${cfg.user} ${cfg.group} - -"
       ];
 
+      environment.etc."charybdis/ircd.conf".source = configFile;
+
       systemd.services.charybdis = {
         description = "Charybdis IRC daemon";
         wantedBy = [ "multi-user.target" ];
+        reloadIfChanged = true;
+        restartTriggers = [
+          configFile
+        ];
         environment = {
           BANDB_DBPATH = "${cfg.statedir}/ban.db";
         };
         serviceConfig = {
-          ExecStart   = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}";
+          ExecStart   = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile /etc/charybdis/ircd.conf";
+          ExecReload = "${coreutils}/bin/kill -HUP $MAINPID";
           Group = cfg.group;
           User = cfg.user;
         };
diff --git a/nixpkgs/nixos/modules/services/networking/consul.nix b/nixpkgs/nixos/modules/services/networking/consul.nix
index 792b2e7f5dfe..ca9c422e6d7c 100644
--- a/nixpkgs/nixos/modules/services/networking/consul.nix
+++ b/nixpkgs/nixos/modules/services/networking/consul.nix
@@ -8,7 +8,9 @@ let
 
   configOptions = {
     data_dir = dataDir;
-    ui = cfg.webUi;
+    ui_config = {
+      enabled = cfg.webUi;
+    };
   } // cfg.extraConfig;
 
   configFiles = [ "/etc/consul.json" "/etc/consul-addrs.json" ]
diff --git a/nixpkgs/nixos/modules/services/networking/coturn.nix b/nixpkgs/nixos/modules/services/networking/coturn.nix
index 610754e9bd39..ce563c31136f 100644
--- a/nixpkgs/nixos/modules/services/networking/coturn.nix
+++ b/nixpkgs/nixos/modules/services/networking/coturn.nix
@@ -193,6 +193,7 @@ in {
       realm = mkOption {
         type = types.str;
         default = config.networking.hostName;
+        defaultText = literalExpression "config.networking.hostName";
         example = "example.com";
         description = ''
           The default realm to be used for the users when no explicit
diff --git a/nixpkgs/nixos/modules/services/networking/croc.nix b/nixpkgs/nixos/modules/services/networking/croc.nix
index 9466adf71d8c..d044979e10df 100644
--- a/nixpkgs/nixos/modules/services/networking/croc.nix
+++ b/nixpkgs/nixos/modules/services/networking/croc.nix
@@ -51,7 +51,7 @@ in
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
-        ProtectProc = "noaccess";
+        ProtectProc = "invisible";
         ProtectSystem = "strict";
         RemoveIPC = true;
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
diff --git a/nixpkgs/nixos/modules/services/networking/ddclient.nix b/nixpkgs/nixos/modules/services/networking/ddclient.nix
index 7820eedd9327..8a2c0fc7080c 100644
--- a/nixpkgs/nixos/modules/services/networking/ddclient.nix
+++ b/nixpkgs/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=$(printf "%q" "$(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/nixpkgs/nixos/modules/services/networking/dhcpcd.nix b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
index 31e4b6ad2988..2c339350acd3 100644
--- a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
@@ -207,13 +207,20 @@ in
 
         serviceConfig =
           { Type = "forking";
-            PIDFile = "/run/dhcpcd.pid";
+            PIDFile = "/run/dhcpcd/pid";
+            RuntimeDirectory = "dhcpcd";
             ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
             ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
             Restart = "always";
           };
       };
 
+    users.users.dhcpcd = {
+      isSystemUser = true;
+      group = "dhcpcd";
+    };
+    users.groups.dhcpcd = {};
+
     environment.systemPackages = [ dhcpcd ];
 
     environment.etc."dhcpcd.exit-hook".source = exitHook;
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
index 400d6e67044e..c2add170e9cc 100644
--- a/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -145,6 +145,7 @@ in {
     providerName = mkOption {
       type = types.str;
       default = "2.dnscrypt-cert.${config.networking.hostName}";
+      defaultText = literalExpression ''"2.dnscrypt-cert.''${config.networking.hostName}"'';
       example = "2.dnscrypt-cert.myresolver";
       description = ''
         The name that will be given to this DNSCrypt resolver.
diff --git a/nixpkgs/nixos/modules/services/networking/ergo.nix b/nixpkgs/nixos/modules/services/networking/ergo.nix
index c52de30dc361..6e55a7cfff6c 100644
--- a/nixpkgs/nixos/modules/services/networking/ergo.nix
+++ b/nixpkgs/nixos/modules/services/networking/ergo.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
   cfg = config.services.ergo;
+  opt = options.services.ergo;
 
-  inherit (lib) mkEnableOption mkIf mkOption optionalString types;
+  inherit (lib) literalExpression mkEnableOption mkIf mkOption optionalString types;
 
   configFile = pkgs.writeText "ergo.conf" (''
 ergo {
@@ -92,6 +93,7 @@ in {
       group = mkOption {
         type = types.str;
         default = cfg.user;
+        defaultText = literalExpression "config.${opt.user}";
         description = "The group as which to run the Ergo node.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
index a2e5b30dc0f0..88b4cd90540f 100644
--- a/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
+++ b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
@@ -67,7 +67,7 @@ in
       eternal-terminal = {
         description = "Eternal Terminal server.";
         wantedBy = [ "multi-user.target" ];
-        after = [ "syslog.target" "network.target" ];
+        after = [ "network.target" ];
         serviceConfig = {
           Type = "forking";
           ExecStart = "${pkgs.eternal-terminal}/bin/etserver --daemon --cfgfile=${pkgs.writeText "et.cfg" ''
diff --git a/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix b/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix
deleted file mode 100644
index 1ad573abfca3..000000000000
--- a/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix
+++ /dev/null
@@ -1,183 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.firefox.syncserver;
-
-  defaultDbLocation = "/var/db/firefox-sync-server/firefox-sync-server.db";
-  defaultSqlUri = "sqlite:///${defaultDbLocation}";
-
-  syncServerIni = pkgs.writeText "syncserver.ini" ''
-    [DEFAULT]
-    overrides = ${cfg.privateConfig}
-
-    [server:main]
-    use = egg:gunicorn
-    host = ${cfg.listen.address}
-    port = ${toString cfg.listen.port}
-
-    [app:main]
-    use = egg:syncserver
-
-    [syncserver]
-    public_url = ${cfg.publicUrl}
-    ${optionalString (cfg.sqlUri != "") "sqluri = ${cfg.sqlUri}"}
-    allow_new_users = ${boolToString cfg.allowNewUsers}
-
-    [browserid]
-    backend = tokenserver.verifiers.LocalVerifier
-    audiences = ${removeSuffix "/" cfg.publicUrl}
-  '';
-
-  user = "syncserver";
-  group = "syncserver";
-in
-
-{
-  meta.maintainers = with lib.maintainers; [ nadrieril ];
-
-  options = {
-    services.firefox.syncserver = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable a Firefox Sync Server, this give the opportunity to
-          Firefox users to store all synchronized data on their own server. To use this
-          server, Firefox users should visit the <option>about:config</option>, and
-          replicate the following change
-
-          <screen>
-          services.sync.tokenServerURI: http://localhost:5000/token/1.0/sync/1.5
-          </screen>
-
-          where <option>http://localhost:5000/</option> corresponds to the
-          public url of the server.
-        '';
-      };
-
-      listen.address = mkOption {
-        type = types.str;
-        default = "127.0.0.1";
-        example = "0.0.0.0";
-        description = ''
-          Address on which the sync server listen to.
-        '';
-      };
-
-      listen.port = mkOption {
-        type = types.port;
-        default = 5000;
-        description = ''
-          Port on which the sync server listen to.
-        '';
-      };
-
-      publicUrl = mkOption {
-        type = types.str;
-        default = "http://localhost:5000/";
-        example = "http://sync.example.com/";
-        description = ''
-          Public URL with which firefox users can use to access the sync server.
-        '';
-      };
-
-      allowNewUsers = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to allow new-user signups on the server. Only request by
-          existing accounts will be honored.
-        '';
-      };
-
-      sqlUri = mkOption {
-        type = types.str;
-        default = defaultSqlUri;
-        example = "postgresql://scott:tiger@localhost/test";
-        description = ''
-          The location of the database. This URL is composed of
-          <option>dialect[+driver]://user:password@host/dbname[?key=value..]</option>,
-          where <option>dialect</option> is a database name such as
-          <option>mysql</option>, <option>oracle</option>, <option>postgresql</option>,
-          etc., and <option>driver</option> the name of a DBAPI, such as
-          <option>psycopg2</option>, <option>pyodbc</option>, <option>cx_oracle</option>,
-          etc. The <link
-          xlink:href="http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html#database-urls">
-          SQLAlchemy documentation</link> provides more examples and describe the syntax of
-          the expected URL.
-        '';
-      };
-
-      privateConfig = mkOption {
-        type = types.str;
-        default = "/etc/firefox/syncserver-secret.ini";
-        description = ''
-          The private config file is used to extend the generated config with confidential
-          information, such as the <option>syncserver.sqlUri</option> setting if it contains a
-          password, and the <option>syncserver.secret</option> setting is used by the server to
-          generate cryptographically-signed authentication tokens.
-
-          If this file does not exist, then it is created with a generated
-          <option>syncserver.secret</option> settings.
-       '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-
-    systemd.services.syncserver = {
-      after = [ "network.target" ];
-      description = "Firefox Sync Server";
-      wantedBy = [ "multi-user.target" ];
-      path = [
-        pkgs.coreutils
-        (pkgs.python.withPackages (ps: [ pkgs.syncserver ps.gunicorn ]))
-      ];
-
-      serviceConfig = {
-        User = user;
-        Group = group;
-        PermissionsStartOnly = true;
-      };
-
-      preStart = ''
-        if ! test -e ${cfg.privateConfig}; then
-          mkdir -p $(dirname ${cfg.privateConfig})
-          echo  > ${cfg.privateConfig} '[syncserver]'
-          chmod 600 ${cfg.privateConfig}
-          echo >> ${cfg.privateConfig} "secret = $(head -c 20 /dev/urandom | sha1sum | tr -d ' -')"
-        fi
-        chmod 600 ${cfg.privateConfig}
-        chmod 755 $(dirname ${cfg.privateConfig})
-        chown ${user}:${group} ${cfg.privateConfig}
-
-      '' + optionalString (cfg.sqlUri == defaultSqlUri) ''
-        if ! test -e $(dirname ${defaultDbLocation}); then
-          mkdir -m 700 -p $(dirname ${defaultDbLocation})
-          chown ${user}:${group} $(dirname ${defaultDbLocation})
-        fi
-
-        # Move previous database file if it exists
-        oldDb="/var/db/firefox-sync-server.db"
-        if test -f $oldDb; then
-          mv $oldDb ${defaultDbLocation}
-          chown ${user}:${group} ${defaultDbLocation}
-        fi
-      '';
-
-      script = ''
-        gunicorn --paste ${syncServerIni}
-      '';
-    };
-
-    users.users.${user} = {
-      inherit group;
-      isSystemUser = true;
-    };
-
-    users.groups.${group} = {};
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/networking/firewall.nix b/nixpkgs/nixos/modules/services/networking/firewall.nix
index b5b46fe6042c..ff023a888f26 100644
--- a/nixpkgs/nixos/modules/services/networking/firewall.nix
+++ b/nixpkgs/nixos/modules/services/networking/firewall.nix
@@ -421,6 +421,7 @@ in
       checkReversePath = mkOption {
         type = types.either types.bool (types.enum ["strict" "loose"]);
         default = kernelHasRPFilter;
+        defaultText = literalDocBook "<literal>true</literal> if supported by the chosen kernel";
         example = "loose";
         description =
           ''
diff --git a/nixpkgs/nixos/modules/services/networking/flannel.nix b/nixpkgs/nixos/modules/services/networking/flannel.nix
index b15339870ee2..ac84b3d35a3d 100644
--- a/nixpkgs/nixos/modules/services/networking/flannel.nix
+++ b/nixpkgs/nixos/modules/services/networking/flannel.nix
@@ -93,6 +93,9 @@ in {
       '';
       type = types.nullOr types.str;
       default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
+      defaultText = literalExpression ''
+        with config.networking; (hostName + optionalString (domain != null) ".''${domain}")
+      '';
       example = "node1.example.com";
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/freeradius.nix b/nixpkgs/nixos/modules/services/networking/freeradius.nix
index f3fdd576b65c..7fa3a8fa17fa 100644
--- a/nixpkgs/nixos/modules/services/networking/freeradius.nix
+++ b/nixpkgs/nixos/modules/services/networking/freeradius.nix
@@ -28,6 +28,7 @@ let
         ProtectHome = "on";
         Restart = "on-failure";
         RestartSec = 2;
+        LogsDirectory = "radius";
     };
   };
 
@@ -73,6 +74,7 @@ in
       users.radius = {
         /*uid = config.ids.uids.radius;*/
         description = "Radius daemon user";
+        isSystemUser = true;
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/i2pd.nix b/nixpkgs/nixos/modules/services/networking/i2pd.nix
index 17828ca44ff2..e1a31a0c2ee0 100644
--- a/nixpkgs/nixos/modules/services/networking/i2pd.nix
+++ b/nixpkgs/nixos/modules/services/networking/i2pd.nix
@@ -224,7 +224,7 @@ let
 
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
-    exec ${pkgs.i2pd}/bin/i2pd \
+    exec ${cfg.package}/bin/i2pd \
       ${if cfg.address == null then "" else "--host="+cfg.address} \
       --service \
       --conf=${i2pdConf} \
@@ -253,6 +253,15 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.i2pd;
+        defaultText = literalExpression "pkgs.i2pd";
+        description = ''
+          i2pd package to use.
+        '';
+      };
+
       logLevel = mkOption {
         type = types.enum ["debug" "info" "warn" "error"];
         default = "error";
diff --git a/nixpkgs/nixos/modules/services/networking/jibri/default.nix b/nixpkgs/nixos/modules/services/networking/jibri/default.nix
new file mode 100644
index 000000000000..113a7aa4384a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/jibri/logging.properties-journal b/nixpkgs/nixos/modules/services/networking/jibri/logging.properties-journal
new file mode 100644
index 000000000000..61eadbfddcb3
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix b/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix
index dd06ad98a973..abb0bd0a25e1 100644
--- a/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix
+++ b/nixpkgs/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -217,6 +217,8 @@ in
         "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "videobridge";
         "-Djava.util.logging.config.file" = "/etc/jitsi/videobridge/logging.properties";
         "-Dconfig.file" = pkgs.writeText "jvb.conf" (toHOCON jvbConfig);
+        # Mitigate CVE-2021-44228
+        "-Dlog4j2.formatMsgNoLookups" = true;
       } // (mapAttrs' (k: v: nameValuePair "-D${k}" v) cfg.extraProperties);
     in
     {
diff --git a/nixpkgs/nixos/modules/services/networking/kea.nix b/nixpkgs/nixos/modules/services/networking/kea.nix
index b11402204aec..4da47f575f79 100644
--- a/nixpkgs/nixos/modules/services/networking/kea.nix
+++ b/nixpkgs/nixos/modules/services/networking/kea.nix
@@ -236,6 +236,7 @@ in
 
       environment = {
         KEA_PIDFILE_DIR = "/run/kea";
+        KEA_LOCKFILE_DIR = "/run/kea";
       };
 
       restartTriggers = [
@@ -271,6 +272,7 @@ in
 
       environment = {
         KEA_PIDFILE_DIR = "/run/kea";
+        KEA_LOCKFILE_DIR = "/run/kea";
       };
 
       restartTriggers = [
@@ -313,6 +315,7 @@ in
 
       environment = {
         KEA_PIDFILE_DIR = "/run/kea";
+        KEA_LOCKFILE_DIR = "/run/kea";
       };
 
       restartTriggers = [
@@ -353,6 +356,7 @@ in
 
       environment = {
         KEA_PIDFILE_DIR = "/run/kea";
+        KEA_LOCKFILE_DIR = "/run/kea";
       };
 
       restartTriggers = [
@@ -361,7 +365,7 @@ in
 
       serviceConfig = {
         ExecStart = "${package}/bin/kea-dhcp-ddns -c /etc/kea/dhcp-ddns.conf ${lib.escapeShellArgs cfg.dhcp-ddns.extraArgs}";
-        AmbientCapabilites = [
+        AmbientCapabilities = [
           "CAP_NET_BIND_SERVICE"
         ];
         CapabilityBoundingSet = [
diff --git a/nixpkgs/nixos/modules/services/networking/knot.nix b/nixpkgs/nixos/modules/services/networking/knot.nix
index 67eadbd76702..a58a03997b3b 100644
--- a/nixpkgs/nixos/modules/services/networking/knot.nix
+++ b/nixpkgs/nixos/modules/services/networking/knot.nix
@@ -80,13 +80,13 @@ in {
   };
 
   config = mkIf config.services.knot.enable {
+    users.groups.knot = {};
     users.users.knot = {
       isSystemUser = true;
       group = "knot";
       description = "Knot daemon user";
     };
 
-    users.groups.knot.gid = null;
     systemd.services.knot = {
       unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
       description = cfg.package.meta.description;
@@ -98,17 +98,52 @@ in {
         Type = "notify";
         ExecStart = "${cfg.package}/bin/knotd --config=${configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}";
         ExecReload = "${knot-cli-wrappers}/bin/knotc reload";
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
-        NoNewPrivileges = true;
         User = "knot";
+        Group = "knot";
+
+        AmbientCapabilities = [
+          "CAP_NET_BIND_SERVICE"
+        ];
+        CapabilityBoundingSet = [
+          "CAP_NET_BIND_SERVICE"
+        ];
+        DeviceAllow = "";
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = false; # breaks capability passing
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        Restart = "on-abort";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime =true;
+        RestrictSUIDSGID = true;
         RuntimeDirectory = "knot";
         StateDirectory = "knot";
         StateDirectoryMode = "0700";
-        PrivateDevices = true;
-        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
         SystemCallArchitectures = "native";
-        Restart = "on-abort";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
       };
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix b/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix
new file mode 100644
index 000000000000..b119ba8acf63
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/lxd-image-server.nix
@@ -0,0 +1,137 @@
+{ 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;
+        extraConfig = ''
+          create 755 lxd-image-server ${cfg.group}
+          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/nixpkgs/nixos/modules/services/networking/monero.nix b/nixpkgs/nixos/modules/services/networking/monero.nix
index 9a9084e4ce1a..8bed89917c85 100644
--- a/nixpkgs/nixos/modules/services/networking/monero.nix
+++ b/nixpkgs/nixos/modules/services/networking/monero.nix
@@ -222,7 +222,7 @@ in
       serviceConfig = {
         User  = "monero";
         Group = "monero";
-        ExecStart = "${pkgs.monero}/bin/monerod --config-file=${configFile} --non-interactive";
+        ExecStart = "${pkgs.monero-cli}/bin/monerod --config-file=${configFile} --non-interactive";
         Restart = "always";
         SuccessExitStatus = [ 0 1 ];
       };
diff --git a/nixpkgs/nixos/modules/services/networking/mosquitto.md b/nixpkgs/nixos/modules/services/networking/mosquitto.md
new file mode 100644
index 000000000000..5cdb598151e5
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/mosquitto.nix b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
index b0fbfc194083..2d498d4dbbcf 100644
--- a/nixpkgs/nixos/modules/services/networking/mosquitto.nix
+++ b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
@@ -5,215 +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;
-        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;
-          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";
@@ -227,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
@@ -252,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
@@ -275,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 = {
@@ -302,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/nixpkgs/nixos/modules/services/networking/mosquitto.xml b/nixpkgs/nixos/modules/services/networking/mosquitto.xml
new file mode 100644
index 000000000000..d16ab28c0269
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/ncdns.nix b/nixpkgs/nixos/modules/services/networking/ncdns.nix
index af17fc0814b2..82c285d05160 100644
--- a/nixpkgs/nixos/modules/services/networking/ncdns.nix
+++ b/nixpkgs/nixos/modules/services/networking/ncdns.nix
@@ -76,6 +76,7 @@ in
       identity.hostname = mkOption {
         type = types.str;
         default = config.networking.hostName;
+        defaultText = literalExpression "config.networking.hostName";
         example = "example.com";
         description = ''
           The hostname of this ncdns instance, which defaults to the machine
diff --git a/nixpkgs/nixos/modules/services/networking/networkmanager.nix b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
index 2a826e0f0870..73e63e2ee99b 100644
--- a/nixpkgs/nixos/modules/services/networking/networkmanager.nix
+++ b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
@@ -502,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 ];
@@ -534,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/nixpkgs/nixos/modules/services/networking/nix-serve.nix b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
index 7fc145f2303d..390f0ddaee83 100644
--- a/nixpkgs/nixos/modules/services/networking/nix-serve.nix
+++ b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
@@ -37,8 +37,6 @@ in
           nix-store --generate-binary-cache-key key-name secret-key-file public-key-file
           ```
 
-          Make sure user `nix-serve` has read access to the private key file.
-
           For more details see <citerefentry><refentrytitle>nix-store</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
         '';
       };
@@ -61,16 +59,22 @@ in
 
       path = [ config.nix.package.out pkgs.bzip2.bin ];
       environment.NIX_REMOTE = "daemon";
-      environment.NIX_SECRET_KEY_FILE = cfg.secretKeyFile;
+
+      script = ''
+        ${lib.optionalString (cfg.secretKeyFile != null) ''
+          export NIX_SECRET_KEY_FILE="$CREDENTIALS_DIRECTORY/NIX_SECRET_KEY_FILE"
+        ''}
+        exec ${pkgs.nix-serve}/bin/nix-serve --listen ${cfg.bindAddress}:${toString cfg.port} ${cfg.extraParams}
+      '';
 
       serviceConfig = {
         Restart = "always";
         RestartSec = "5s";
-        ExecStart = "${pkgs.nix-serve}/bin/nix-serve " +
-          "--listen ${cfg.bindAddress}:${toString cfg.port} ${cfg.extraParams}";
         User = "nix-serve";
         Group = "nix-serve";
         DynamicUser = true;
+        LoadCredential = lib.optionalString (cfg.secretKeyFile != null)
+          "NIX_SECRET_KEY_FILE:${cfg.secretKeyFile}";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/networking/nomad.nix b/nixpkgs/nixos/modules/services/networking/nomad.nix
index 3bd15bd5c808..43333af5e2fe 100644
--- a/nixpkgs/nixos/modules/services/networking/nomad.nix
+++ b/nixpkgs/nixos/modules/services/networking/nomad.nix
@@ -51,7 +51,7 @@ in
 
       extraSettingsPaths = mkOption {
         type = types.listOf types.path;
-        default = [];
+        default = [ ];
         description = ''
           Additional settings paths used to configure nomad. These can be files or directories.
         '';
@@ -60,9 +60,21 @@ in
         '';
       };
 
+      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.
@@ -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/nixpkgs/nixos/modules/services/networking/nsd.nix b/nixpkgs/nixos/modules/services/networking/nsd.nix
index 893995165b9e..cf6c9661dc1b 100644
--- a/nixpkgs/nixos/modules/services/networking/nsd.nix
+++ b/nixpkgs/nixos/modules/services/networking/nsd.nix
@@ -603,6 +603,7 @@ in
     reuseport = mkOption {
       type = types.bool;
       default = pkgs.stdenv.isLinux;
+      defaultText = literalExpression "pkgs.stdenv.isLinux";
       description = ''
         Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
         processes bind to the same port. This speeds up operation especially
diff --git a/nixpkgs/nixos/modules/services/networking/ntopng.nix b/nixpkgs/nixos/modules/services/networking/ntopng.nix
index c15257117137..77a004e8ab3a 100644
--- a/nixpkgs/nixos/modules/services/networking/ntopng.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntopng.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
 
   cfg = config.services.ntopng;
+  opt = options.services.ntopng;
   redisCfg = config.services.redis;
 
   configFile = if cfg.configText != "" then
@@ -35,8 +36,8 @@ in
           collection tool.
 
           With the default configuration, ntopng monitors all network
-          interfaces and displays its findings at http://localhost:${toString
-          cfg.http-port}. Default username and password is admin/admin.
+          interfaces and displays its findings at http://localhost:''${toString
+          config.${opt.http-port}}. Default username and password is admin/admin.
 
           See the ntopng(8) manual page and http://www.ntop.org/products/ntop/
           for more info.
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
index d414936a2c2b..34728455a212 100644
--- a/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
@@ -52,6 +52,7 @@ in
 
       servers = mkOption {
         default = config.networking.timeServers;
+        defaultText = literalExpression "config.networking.timeServers";
         type = types.listOf types.str;
         description = ''
           The set of NTP servers from which to synchronise.
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
index ce4802ce0245..12be0d045a85 100644
--- a/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
@@ -77,6 +77,7 @@ in
 
       servers = mkOption {
         default = config.networking.timeServers;
+        defaultText = literalExpression "config.networking.timeServers";
         type = types.listOf types.str;
         description = ''
           The set of NTP servers from which to synchronise.
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
index 9f3892e3b538..e86b71291f96 100644
--- a/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
+++ b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
@@ -23,6 +23,7 @@ in
 
     servers = mkOption {
       default = config.services.ntp.servers;
+      defaultText = literalExpression "config.services.ntp.servers";
       type = types.listOf types.str;
       inherit (options.services.ntp.servers) description;
     };
diff --git a/nixpkgs/nixos/modules/services/networking/pleroma.nix b/nixpkgs/nixos/modules/services/networking/pleroma.nix
index 2f32faf387ca..9b8382392c0a 100644
--- a/nixpkgs/nixos/modules/services/networking/pleroma.nix
+++ b/nixpkgs/nixos/modules/services/networking/pleroma.nix
@@ -100,6 +100,7 @@ in {
       after = [ "network-online.target" "postgresql.service" ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ];
+      environment.RELEASE_COOKIE = "/var/lib/pleroma/.cookie";
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
@@ -116,8 +117,14 @@ in {
         # has not been updated. But the no-op process is pretty fast.
         # Better be safe than sorry migration-wise.
         ExecStartPre =
-          let preScript = pkgs.writers.writeBashBin "pleromaStartPre"
-            "${cfg.package}/bin/pleroma_ctl migrate";
+          let preScript = pkgs.writers.writeBashBin "pleromaStartPre" ''
+            if [ ! -f /var/lib/pleroma/.cookie ]
+            then
+              echo "Creating cookie file"
+              dd if=/dev/urandom bs=1 count=16 | hexdump -e '16/1 "%02x"' > /var/lib/pleroma/.cookie
+            fi
+            ${cfg.package}/bin/pleroma_ctl migrate
+          '';
           in "${preScript}/bin/pleromaStartPre";
 
         ExecStart = "${cfg.package}/bin/pleroma start";
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.xml b/nixpkgs/nixos/modules/services/networking/prosody.xml
index 471240cd1475..6358d744ff78 100644
--- a/nixpkgs/nixos/modules/services/networking/prosody.xml
+++ b/nixpkgs/nixos/modules/services/networking/prosody.xml
@@ -72,7 +72,7 @@ services.prosody = {
    a TLS certificate for the three endponits:
     <programlisting>
 security.acme = {
-  <link linkend="opt-security.acme.email">email</link> = "root@example.org";
+  <link linkend="opt-security.acme.defaults.email">email</link> = "root@example.org";
   <link linkend="opt-security.acme.acceptTerms">acceptTerms</link> = true;
   <link linkend="opt-security.acme.certs">certs</link> = {
     "example.org" = {
diff --git a/nixpkgs/nixos/modules/services/networking/quassel.nix b/nixpkgs/nixos/modules/services/networking/quassel.nix
index 22940ef7a13a..844c9a6b8b35 100644
--- a/nixpkgs/nixos/modules/services/networking/quassel.nix
+++ b/nixpkgs/nixos/modules/services/networking/quassel.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.quassel;
+  opt = options.services.quassel;
   quassel = cfg.package;
   user = if cfg.user != null then cfg.user else "quassel";
 in
@@ -63,6 +64,9 @@ in
 
       dataDir = mkOption {
         default = "/home/${user}/.config/quassel-irc.org";
+        defaultText = literalExpression ''
+          "/home/''${config.${opt.user}}/.config/quassel-irc.org"
+        '';
         type = types.str;
         description = ''
           The directory holding configuration files, the SQlite database and the SSL Cert.
diff --git a/nixpkgs/nixos/modules/services/networking/quorum.nix b/nixpkgs/nixos/modules/services/networking/quorum.nix
index 50148dc314da..bddcd18c7fbe 100644
--- a/nixpkgs/nixos/modules/services/networking/quorum.nix
+++ b/nixpkgs/nixos/modules/services/networking/quorum.nix
@@ -1,9 +1,10 @@
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 let
 
   inherit (lib) mkEnableOption mkIf mkOption literalExpression types optionalString;
 
   cfg = config.services.quorum;
+  opt = options.services.quorum;
   dataDir = "/var/lib/quorum";
   genesisFile = pkgs.writeText "genesis.json" (builtins.toJSON cfg.genesis);
   staticNodesFile = pkgs.writeText "static-nodes.json" (builtins.toJSON cfg.staticNodes);
@@ -23,6 +24,7 @@ in {
       group = mkOption {
         type = types.str;
         default = cfg.user;
+        defaultText = literalExpression "config.${opt.user}";
         description = "The group as which to run quorum.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/radicale.nix b/nixpkgs/nixos/modules/services/networking/radicale.nix
index c121008d5294..c6c40777ed7c 100644
--- a/nixpkgs/nixos/modules/services/networking/radicale.nix
+++ b/nixpkgs/nixos/modules/services/networking/radicale.nix
@@ -195,6 +195,7 @@ in {
         SystemCallArchitectures = "native";
         SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
         UMask = "0027";
+        WorkingDirectory = "/var/lib/radicale";
       };
     };
   };
diff --git a/nixpkgs/nixos/modules/services/networking/resilio.nix b/nixpkgs/nixos/modules/services/networking/resilio.nix
index 4701b0e8143d..891278506417 100644
--- a/nixpkgs/nixos/modules/services/networking/resilio.nix
+++ b/nixpkgs/nixos/modules/services/networking/resilio.nix
@@ -58,6 +58,7 @@ in
         type = types.str;
         example = "Voltron";
         default = config.networking.hostName;
+        defaultText = literalExpression "config.networking.hostName";
         description = ''
           Name of the Resilio Sync device.
         '';
diff --git a/nixpkgs/nixos/modules/services/networking/sabnzbd.nix b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
index 43566dfd25c5..54eeba1a9ec1 100644
--- a/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/seafile.nix b/nixpkgs/nixos/modules/services/networking/seafile.nix
new file mode 100644
index 000000000000..d7fb22edebed
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/seafile.nix
@@ -0,0 +1,291 @@
+{ 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;
+      defaultText = literalExpression "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/nixpkgs/nixos/modules/services/networking/shairport-sync.nix b/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
index ac526c0e9f6f..eb61663e4d92 100644
--- a/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
+++ b/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
@@ -36,6 +36,14 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to automatically open ports in the firewall.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "shairport";
@@ -45,6 +53,15 @@ in
         '';
       };
 
+      group = mkOption {
+        type = types.str;
+        default = "shairport";
+        description = ''
+          Group account name under which to run shairport-sync. The account
+          will be created.
+        '';
+      };
+
     };
 
   };
@@ -58,13 +75,22 @@ in
     services.avahi.publish.enable = true;
     services.avahi.publish.userServices = true;
 
-    users.users.${cfg.user} =
-      { description = "Shairport user";
+    users = {
+      users.${cfg.user} = {
+        description = "Shairport user";
         isSystemUser = true;
         createHome = true;
         home = "/var/lib/shairport-sync";
+        group = cfg.group;
         extraGroups = [ "audio" ] ++ optional config.hardware.pulseaudio.enable "pulse";
       };
+      groups.${cfg.group} = {};
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 5000 ];
+      allowedUDPPortRanges = [ { from = 6001; to = 6011; } ];
+    };
 
     systemd.services.shairport-sync =
       {
@@ -73,6 +99,7 @@ in
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
           User = cfg.user;
+          Group = cfg.group;
           ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync ${cfg.arguments}";
           RuntimeDirectory = "shairport-sync";
         };
diff --git a/nixpkgs/nixos/modules/services/networking/skydns.nix b/nixpkgs/nixos/modules/services/networking/skydns.nix
index c4e959b57bbe..dea60a3862a3 100644
--- a/nixpkgs/nixos/modules/services/networking/skydns.nix
+++ b/nixpkgs/nixos/modules/services/networking/skydns.nix
@@ -49,6 +49,7 @@ in {
 
     nameservers = mkOption {
       default = map (n: n + ":53") config.networking.nameservers;
+      defaultText = literalExpression ''map (n: n + ":53") config.networking.nameservers'';
       type = types.listOf types.str;
       description = "Skydns list of nameservers to forward DNS requests to when not authoritative for a domain.";
       example = ["8.8.8.8:53" "8.8.4.4:53"];
diff --git a/nixpkgs/nixos/modules/services/networking/smokeping.nix b/nixpkgs/nixos/modules/services/networking/smokeping.nix
index 021368488a3f..bd71b158dbe3 100644
--- a/nixpkgs/nixos/modules/services/networking/smokeping.nix
+++ b/nixpkgs/nixos/modules/services/networking/smokeping.nix
@@ -131,10 +131,15 @@ in
       };
       imgUrl = mkOption {
         type = types.str;
-        default = "http://${cfg.hostName}:${toString cfg.port}/cache";
-        defaultText = literalExpression ''"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"];
@@ -167,6 +172,17 @@ in
         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;
@@ -225,6 +241,12 @@ in
           + FPing
           binary = ${config.security.wrapperDir}/fping
         '';
+        defaultText = literalExpression ''
+          '''
+            + FPing
+            binary = ''${config.security.wrapperDir}/fping
+          '''
+        '';
         description = "Probe configuration";
       };
       sendmail = mkOption {
@@ -297,10 +319,11 @@ in
     };
     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
@@ -311,18 +334,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/nixpkgs/nixos/modules/services/networking/soju.nix b/nixpkgs/nixos/modules/services/networking/soju.nix
index 68a33e9dccba..cb0acf4765ff 100644
--- a/nixpkgs/nixos/modules/services/networking/soju.nix
+++ b/nixpkgs/nixos/modules/services/networking/soju.nix
@@ -43,6 +43,7 @@ in
     hostName = mkOption {
       type = types.str;
       default = config.networking.hostName;
+      defaultText = literalExpression "config.networking.hostName";
       description = "Server hostname.";
     };
 
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index 8ae62931a8f9..cca61b9ce930 100644
--- a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/stubby.nix b/nixpkgs/nixos/modules/services/networking/stubby.nix
index c5e0f929a126..78c13798dde2 100644
--- a/nixpkgs/nixos/modules/services/networking/stubby.nix
+++ b/nixpkgs/nixos/modules/services/networking/stubby.nix
@@ -1,180 +1,51 @@
-{ config, lib, pkgs, ...}:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.stubby;
+  settingsFormat = pkgs.formats.yaml { };
+  confFile = settingsFormat.generate "stubby.yml" cfg.settings;
+in {
+  imports = map (x:
+    (mkRemovedOptionModule [ "services" "stubby" x ]
+      "Stubby configuration moved to services.stubby.settings.")) [
+        "authenticationMode"
+        "fallbackProtocols"
+        "idleTimeout"
+        "listenAddresses"
+        "queryPaddingBlocksize"
+        "roundRobinUpstreams"
+        "subnetPrivate"
+        "upstreamServers"
+      ];
 
-  fallbacks = concatMapStringsSep "\n  " (x: "- ${x}") cfg.fallbackProtocols;
-  listeners = concatMapStringsSep "\n  " (x: "- ${x}") cfg.listenAddresses;
-
-  # By default, the recursive resolvers maintained by the getdns
-  # project itself are enabled. More information about both getdns's servers,
-  # as well as third party options for upstream resolvers, can be found here:
-  # https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers
-  #
-  # You can override these values by supplying a yaml-formatted array of your
-  # preferred upstream resolvers in the following format:
-  #
-  # 106 # - address_data: IPv4 or IPv6 address of the upstream
-  #   port: Port for UDP/TCP (default is 53)
-  #   tls_auth_name: Authentication domain name checked against the server
-  #                  certificate
-  #   tls_pubkey_pinset: An SPKI pinset verified against the keys in the server
-  #                      certificate
-  #     - digest: Only "sha256" is currently supported
-  #       value: Base64 encoded value of the sha256 fingerprint of the public
-  #              key
-  #   tls_port: Port for TLS (default is 853)
-
-  defaultUpstream = ''
-    - address_data: 145.100.185.15
-      tls_auth_name: "dnsovertls.sinodun.com"
-      tls_pubkey_pinset:
-        - digest: "sha256"
-          value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
-    - address_data: 145.100.185.16
-      tls_auth_name: "dnsovertls1.sinodun.com"
-      tls_pubkey_pinset:
-        - digest: "sha256"
-          value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
-    - address_data: 185.49.141.37
-      tls_auth_name: "getdnsapi.net"
-      tls_pubkey_pinset:
-        - digest: "sha256"
-          value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
-    - address_data: 2001:610:1:40ba:145:100:185:15
-      tls_auth_name: "dnsovertls.sinodun.com"
-      tls_pubkey_pinset:
-        - digest: "sha256"
-          value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
-    - address_data: 2001:610:1:40ba:145:100:185:16
-      tls_auth_name: "dnsovertls1.sinodun.com"
-      tls_pubkey_pinset:
-        - digest: "sha256"
-          value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
-    - address_data: 2a04:b900:0:100::38
-      tls_auth_name: "getdnsapi.net"
-      tls_pubkey_pinset:
-        - digest: "sha256"
-          value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
-  '';
-
-  # Resolution type is not changeable here because it is required per the
-  # stubby documentation:
-  #
-  # "resolution_type: Work in stub mode only (not recursive mode) - required for Stubby
-  # operation."
-  #
-  # https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby
-
-  confFile = pkgs.writeText "stubby.yml" ''
-    resolution_type: GETDNS_RESOLUTION_STUB
-    dns_transport_list:
-      ${fallbacks}
-    appdata_dir: "/var/cache/stubby"
-    tls_authentication: ${cfg.authenticationMode}
-    tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize}
-    edns_client_subnet_private: ${if cfg.subnetPrivate then "1" else "0"}
-    idle_timeout: ${toString cfg.idleTimeout}
-    listen_addresses:
-      ${listeners}
-    round_robin_upstreams: ${if cfg.roundRobinUpstreams then "1" else "0"}
-    ${cfg.extraConfig}
-    upstream_recursive_servers:
-    ${cfg.upstreamServers}
-  '';
-in
-
-{
   options = {
     services.stubby = {
 
       enable = mkEnableOption "Stubby DNS resolver";
 
-      fallbackProtocols = mkOption {
-        default = [ "GETDNS_TRANSPORT_TLS" ];
-        type = with types; listOf (enum [
-          "GETDNS_TRANSPORT_TLS"
-          "GETDNS_TRANSPORT_TCP"
-          "GETDNS_TRANSPORT_UDP"
-        ]);
-        description = ''
-          Ordered list composed of one or more transport protocols.
-          Strict mode should only use <literal>GETDNS_TRANSPORT_TLS</literal>.
-          Other options are <literal>GETDNS_TRANSPORT_UDP</literal> and
-          <literal>GETDNS_TRANSPORT_TCP</literal>.
+      settings = mkOption {
+        type = types.attrsOf settingsFormat.type;
+        example = lib.literalExpression ''
+          pkgs.stubby.passthru.settingsExample // {
+            upstream_recursive_servers = [{
+              address_data = "158.64.1.29";
+              tls_auth_name = "kaitain.restena.lu";
+              tls_pubkey_pinset = [{
+                digest = "sha256";
+                value = "7ftvIkA+UeN/ktVkovd/7rPZ6mbkhVI7/8HnFJIiLa4=";
+              }];
+            }];
+          };
         '';
-      };
-
-      authenticationMode = mkOption {
-        default = "GETDNS_AUTHENTICATION_REQUIRED";
-        type = types.enum [
-          "GETDNS_AUTHENTICATION_REQUIRED"
-          "GETDNS_AUTHENTICATION_NONE"
-        ];
         description = ''
-          Selects the Strict or Opportunistic usage profile.
-          For strict, set to <literal>GETDNS_AUTHENTICATION_REQUIRED</literal>.
-          for opportunistic, use <literal>GETDNS_AUTHENTICATION_NONE</literal>.
-        '';
-      };
-
-      queryPaddingBlocksize = mkOption {
-        default = 128;
-        type = types.int;
-        description = ''
-          EDNS0 option to pad the size of the DNS query to the given blocksize.
-        '';
-      };
-
-      subnetPrivate = mkOption {
-        default = true;
-        type = types.bool;
-        description = ''
-          EDNS0 option for ECS client privacy. Default is
-          <literal>true</literal>. If set, this option prevents the client
-          subnet from being sent to authoritative nameservers.
-        '';
-      };
-
-      idleTimeout = mkOption {
-        default = 10000;
-        type = types.int;
-        description = "EDNS0 option for keepalive idle timeout expressed in
-        milliseconds.";
-      };
-
-      listenAddresses = mkOption {
-        default = [ "127.0.0.1" "0::1" ];
-        type = with types; listOf str;
-        description = ''
-          Sets the listen address for the stubby daemon.
-          Uses port 53 by default.
-          Ise IP@port to specify a different port.
-        '';
-      };
-
-      roundRobinUpstreams = mkOption {
-        default = true;
-        type = types.bool;
-        description = ''
-          Instructs stubby to distribute queries across all available name
-          servers. Default is <literal>true</literal>. Set to
-          <literal>false</literal> in order to use the first available.
-        '';
-      };
-
-      upstreamServers = mkOption {
-        default = defaultUpstream;
-        type = types.lines;
-        description = ''
-          Replace default upstreams. See <citerefentry><refentrytitle>stubby
-          </refentrytitle><manvolnum>1</manvolnum></citerefentry> for an
-          example of the entry formatting. In Strict mode, at least one of the
-          following settings must be supplied for each nameserver:
-          <literal>tls_auth_name</literal> or
-          <literal>tls_pubkey_pinset</literal>.
+          Content of the Stubby configuration file. All Stubby settings may be set or queried
+          here. The default settings are available at
+          <literal>pkgs.stubby.passthru.settingsExample</literal>. See
+          <link xlink:href="https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby"/>.
+          A list of the public recursive servers can be found here:
+          <link xlink:href="https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers"/>.
         '';
       };
 
@@ -184,20 +55,21 @@ in
         description = "Enable or disable debug level logging.";
       };
 
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        description = ''
-          Add additional configuration options. see <citerefentry>
-          <refentrytitle>stubby</refentrytitle><manvolnum>1</manvolnum>
-          </citerefentry>for more options.
-        '';
-      };
     };
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.stubby ];
+    assertions = [{
+      assertion =
+        (cfg.settings.resolution_type or "") == "GETDNS_RESOLUTION_STUB";
+      message = ''
+        services.stubby.settings.resolution_type must be set to "GETDNS_RESOLUTION_STUB".
+        Is services.stubby.settings unset?
+      '';
+    }];
+
+    services.stubby.settings.appdata_dir = "/var/cache/stubby";
+
     systemd.services.stubby = {
       description = "Stubby local DNS resolver";
       after = [ "network.target" ];
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing.nix b/nixpkgs/nixos/modules/services/networking/syncthing.nix
index 8c44687a3822..e37e324019e8 100644
--- a/nixpkgs/nixos/modules/services/networking/syncthing.nix
+++ b/nixpkgs/nixos/modules/services/networking/syncthing.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.syncthing;
+  opt = options.services.syncthing;
   defaultUser = "syncthing";
   defaultGroup = defaultUser;
 
@@ -431,7 +432,26 @@ in {
           The path where the settings and keys will exist.
         '';
         default = cfg.dataDir + optionalString cond "/.config/syncthing";
-        defaultText = literalExpression "dataDir${optionalString cond " + \"/.config/syncthing\""}";
+        defaultText = literalDocBook ''
+          <variablelist>
+            <varlistentry>
+              <term><literal>stateVersion >= 19.03</literal></term>
+              <listitem>
+                <programlisting>
+                  config.${opt.dataDir} + "/.config/syncthing"
+                </programlisting>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term>otherwise</term>
+              <listitem>
+                <programlisting>
+                  config.${opt.dataDir}
+                </programlisting>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+        '';
       };
 
       extraFlags = mkOption {
diff --git a/nixpkgs/nixos/modules/services/networking/teamspeak3.nix b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
index fadb32dcd777..c0ed08282aaf 100644
--- a/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/networking/tetrd.nix b/nixpkgs/nixos/modules/services/networking/tetrd.nix
new file mode 100644
index 000000000000..ead73c497764
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tetrd.nix
@@ -0,0 +1,96 @@
+{ config, lib, pkgs, ... }:
+
+{
+  options.services.tetrd.enable = lib.mkEnableOption pkgs.tetrd.meta.description;
+
+  config = lib.mkIf config.services.tetrd.enable {
+    environment = {
+      systemPackages = [ pkgs.tetrd ];
+      etc."resolv.conf".source = "/etc/tetrd/resolv.conf";
+    };
+
+    systemd = {
+      tmpfiles.rules = [ "f /etc/tetrd/resolv.conf - - -" ];
+
+      services.tetrd = {
+        description = pkgs.tetrd.meta.description;
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          ExecStart = "${pkgs.tetrd}/opt/Tetrd/bin/tetrd";
+          Restart = "always";
+          RuntimeDirectory = "tetrd";
+          RootDirectory = "/run/tetrd";
+          DynamicUser = true;
+          UMask = "006";
+          DeviceAllow = "usb_device";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateMounts = true;
+          PrivateNetwork = lib.mkDefault false;
+          PrivateTmp = true;
+          PrivateUsers = lib.mkDefault false;
+          ProtectClock = lib.mkDefault false;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+
+          SystemCallFilter = [
+            "@system-service"
+            "~@aio"
+            "~@chown"
+            "~@clock"
+            "~@cpu-emulation"
+            "~@debug"
+            "~@keyring"
+            "~@memlock"
+            "~@module"
+            "~@mount"
+            "~@obsolete"
+            "~@pkey"
+            "~@raw-io"
+            "~@reboot"
+            "~@swap"
+            "~@sync"
+          ];
+
+          BindReadOnlyPaths = [
+            builtins.storeDir
+            "/etc/ssl"
+            "/etc/static/ssl"
+            "${pkgs.nettools}/bin/route:/usr/bin/route"
+            "${pkgs.nettools}/bin/ifconfig:/usr/bin/ifconfig"
+          ];
+
+          BindPaths = [
+            "/etc/tetrd/resolv.conf:/etc/resolv.conf"
+            "/run"
+            "/var/log"
+          ];
+
+          CapabilityBoundingSet = [
+            "CAP_DAC_OVERRIDE"
+            "CAP_NET_ADMIN"
+          ];
+
+          AmbientCapabilities = [
+            "CAP_DAC_OVERRIDE"
+            "CAP_NET_ADMIN"
+          ];
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tinc.nix b/nixpkgs/nixos/modules/services/networking/tinc.nix
index 1d77503d68bc..9db433fa0735 100644
--- a/nixpkgs/nixos/modules/services/networking/tinc.nix
+++ b/nixpkgs/nixos/modules/services/networking/tinc.nix
@@ -289,13 +289,13 @@ in
             };
 
             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.
               '';
             };
 
diff --git a/nixpkgs/nixos/modules/services/networking/unifi.nix b/nixpkgs/nixos/modules/services/networking/unifi.nix
index caf89c84397f..a683c537f05b 100644
--- a/nixpkgs/nixos/modules/services/networking/unifi.nix
+++ b/nixpkgs/nixos/modules/services/networking/unifi.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, options, lib, pkgs, utils, ... }:
 with lib;
 let
   cfg = config.services.unifi;
@@ -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
 {
 
@@ -68,17 +49,7 @@ in
       '';
     };
 
-    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 {
+    services.unifi.openFirewall = mkOption {
       type = types.bool;
       default = true;
       description = ''
@@ -114,6 +85,10 @@ in
 
   config = mkIf cfg.enable {
 
+    warnings = optional
+      (options.services.unifi.openFirewall.highestPrio >= (mkOptionDefault null).priority)
+      "The current services.unifi.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning.";
+
     users.users.unifi = {
       isSystemUser = true;
       group = "unifi";
@@ -122,7 +97,7 @@ in
     };
     users.groups.unifi = {};
 
-    networking.firewall = mkIf cfg.openPorts {
+    networking.firewall = mkIf cfg.openFirewall {
       # https://help.ubnt.com/hc/en-us/articles/218506997
       allowedTCPPorts = [
         8080  # Port for UAP to inform controller.
@@ -136,32 +111,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
@@ -209,8 +163,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;
@@ -220,6 +193,10 @@ in
     };
 
   };
+  imports = [
+    (mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data" )
+    (mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
+  ];
 
   meta.maintainers = with lib.maintainers; [ erictapen pennae ];
 }
diff --git a/nixpkgs/nixos/modules/services/networking/wasabibackend.nix b/nixpkgs/nixos/modules/services/networking/wasabibackend.nix
index 8482823e197f..b6dcd940915a 100644
--- a/nixpkgs/nixos/modules/services/networking/wasabibackend.nix
+++ b/nixpkgs/nixos/modules/services/networking/wasabibackend.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
   cfg = config.services.wasabibackend;
+  opt = options.services.wasabibackend;
 
-  inherit (lib) mkEnableOption mkIf mkOption optionalAttrs optionalString types;
+  inherit (lib) literalExpression mkEnableOption mkIf mkOption optionalAttrs optionalString types;
 
   confOptions = {
       BitcoinRpcConnectionString = "${cfg.rpc.user}:${cfg.rpc.password}";
@@ -103,6 +104,7 @@ in {
       group = mkOption {
         type = types.str;
         default = cfg.user;
+        defaultText = literalExpression "config.${opt.user}";
         description = "The group as which to run the wasabibackend node.";
       };
     };
diff --git a/nixpkgs/nixos/modules/services/networking/wireguard.nix b/nixpkgs/nixos/modules/services/networking/wireguard.nix
index 55b84935b6cb..7cd44b2f8a0a 100644
--- a/nixpkgs/nixos/modules/services/networking/wireguard.nix
+++ b/nixpkgs/nixos/modules/services/networking/wireguard.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
 
   cfg = config.networking.wireguard;
+  opt = options.networking.wireguard;
 
   kernel = config.boot.kernelPackages;
 
@@ -438,6 +439,7 @@ in
         type = types.bool;
         # 2019-05-25: Backwards compatibility.
         default = cfg.interfaces != {};
+        defaultText = literalExpression "config.${opt.interfaces} != { }";
         example = true;
       };
 
diff --git a/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
index 4aa350d21a2b..07dec8ea7181 100644
--- a/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, options, pkgs, utils, ... }:
 
 with lib;
 
@@ -8,6 +8,7 @@ let
     else pkgs.wpa_supplicant;
 
   cfg = config.networking.wireless;
+  opt = options.networking.wireless;
 
   # Content of wpa_supplicant.conf
   generatedConfig = concatStringsSep "\n" (
@@ -421,6 +422,7 @@ in {
       dbusControlled = mkOption {
         type = types.bool;
         default = lib.length cfg.interfaces < 2;
+        defaultText = literalExpression "length config.${opt.interfaces} < 2";
         description = ''
           Whether to enable the DBus control interface.
           This is only needed when using NetworkManager or connman.
diff --git a/nixpkgs/nixos/modules/services/networking/xrdp.nix b/nixpkgs/nixos/modules/services/networking/xrdp.nix
index c4f828f3c5a6..e9f123a181ae 100644
--- a/nixpkgs/nixos/modules/services/networking/xrdp.nix
+++ b/nixpkgs/nixos/modules/services/networking/xrdp.nix
@@ -97,6 +97,11 @@ in
         '';
       };
 
+      confDir = mkOption {
+        type = types.path;
+        default = confDir;
+        description = "The location of the config files for xrdp.";
+      };
     };
   };
 
@@ -149,7 +154,7 @@ in
           User = "xrdp";
           Group = "xrdp";
           PermissionsStartOnly = true;
-          ExecStart = "${cfg.package}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${confDir}/xrdp.ini";
+          ExecStart = "${cfg.package}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${cfg.confDir}/xrdp.ini";
         };
       };
 
@@ -159,7 +164,7 @@ in
         description = "xrdp session manager";
         restartIfChanged = false; # do not restart on "nixos-rebuild switch". like "display-manager", it can have many interactive programs as children
         serviceConfig = {
-          ExecStart = "${cfg.package}/bin/xrdp-sesman --nodaemon --config ${confDir}/sesman.ini";
+          ExecStart = "${cfg.package}/bin/xrdp-sesman --nodaemon --config ${cfg.confDir}/sesman.ini";
           ExecStop  = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
         };
       };
diff --git a/nixpkgs/nixos/modules/services/search/kibana.nix b/nixpkgs/nixos/modules/services/search/kibana.nix
index 381f5156ceb6..e4ab85be9ef1 100644
--- a/nixpkgs/nixos/modules/services/search/kibana.nix
+++ b/nixpkgs/nixos/modules/services/search/kibana.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.kibana;
+  opt = options.services.kibana;
 
   ge7 = builtins.compareVersions cfg.package.version "7" >= 0;
   lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
@@ -130,6 +131,9 @@ in {
           This defaults to the singleton list [ca] when the <option>ca</option> option is defined.
         '';
         default = if cfg.elasticsearch.ca == null then [] else [ca];
+        defaultText = literalExpression ''
+          if config.${opt.elasticsearch.ca} == null then [ ] else [ ca ]
+        '';
         type = types.listOf types.path;
       };
 
diff --git a/nixpkgs/nixos/modules/services/security/aesmd.nix b/nixpkgs/nixos/modules/services/security/aesmd.nix
new file mode 100644
index 000000000000..bb53bc49e259
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/aesmd.nix
@@ -0,0 +1,227 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.services.aesmd;
+
+  sgx-psw = pkgs.sgx-psw.override { inherit (cfg) debug; };
+
+  configFile = with cfg.settings; pkgs.writeText "aesmd.conf" (
+    concatStringsSep "\n" (
+      optional (whitelistUrl != null) "whitelist url = ${whitelistUrl}" ++
+      optional (proxy != null) "aesm proxy = ${proxy}" ++
+      optional (proxyType != null) "proxy type = ${proxyType}" ++
+      optional (defaultQuotingType != null) "default quoting type = ${defaultQuotingType}" ++
+      # Newline at end of file
+      [ "" ]
+    )
+  );
+in
+{
+  options.services.aesmd = {
+    enable = mkEnableOption "Intel's Architectural Enclave Service Manager (AESM) for Intel SGX";
+    debug = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether to build the PSW package in debug mode.";
+    };
+    settings = mkOption {
+      description = "AESM configuration";
+      default = { };
+      type = types.submodule {
+        options.whitelistUrl = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "http://whitelist.trustedservices.intel.com/SGX/LCWL/Linux/sgx_white_list_cert.bin";
+          description = "URL to retrieve authorized Intel SGX enclave signers.";
+        };
+        options.proxy = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "http://proxy_url:1234";
+          description = "HTTP network proxy.";
+        };
+        options.proxyType = mkOption {
+          type = with types; nullOr (enum [ "default" "direct" "manual" ]);
+          default = if (cfg.settings.proxy != null) then "manual" else null;
+          example = "default";
+          description = ''
+            Type of proxy to use. The <literal>default</literal> uses the system's default proxy.
+            If <literal>direct</literal> is given, uses no proxy.
+            A value of <literal>manual</literal> uses the proxy from
+            <option>services.aesmd.settings.proxy</option>.
+          '';
+        };
+        options.defaultQuotingType = mkOption {
+          type = with types; nullOr (enum [ "ecdsa_256" "epid_linkable" "epid_unlinkable" ]);
+          default = null;
+          example = "ecdsa_256";
+          description = "Attestation quote type.";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [{
+      assertion = !(config.boot.specialFileSystems."/dev".options ? "noexec");
+      message = "SGX requires exec permission for /dev";
+    }];
+
+    hardware.cpu.intel.sgx.provision.enable = true;
+
+    systemd.services.aesmd =
+      let
+        storeAesmFolder = "${sgx-psw}/aesm";
+        # Hardcoded path AESM_DATA_FOLDER in psw/ae/aesm_service/source/oal/linux/aesm_util.cpp
+        aesmDataFolder = "/var/opt/aesmd/data";
+        aesmStateDirSystemd = "%S/aesmd";
+      in
+      {
+        description = "Intel Architectural Enclave Service Manager";
+        wantedBy = [ "multi-user.target" ];
+
+        after = [
+          "auditd.service"
+          "network.target"
+          "syslog.target"
+        ];
+
+        environment = {
+          NAME = "aesm_service";
+          AESM_PATH = storeAesmFolder;
+          LD_LIBRARY_PATH = storeAesmFolder;
+        };
+
+        # Make sure any of the SGX application enclave devices is available
+        unitConfig.AssertPathExists = [
+          # legacy out-of-tree driver
+          "|/dev/isgx"
+          # DCAP driver
+          "|/dev/sgx/enclave"
+          # in-tree driver
+          "|/dev/sgx_enclave"
+        ];
+
+        serviceConfig = rec {
+          ExecStartPre = pkgs.writeShellScript "copy-aesmd-data-files.sh" ''
+            set -euo pipefail
+            whiteListFile="${aesmDataFolder}/white_list_cert_to_be_verify.bin"
+            if [[ ! -f "$whiteListFile" ]]; then
+              ${pkgs.coreutils}/bin/install -m 644 -D \
+                "${storeAesmFolder}/data/white_list_cert_to_be_verify.bin" \
+                "$whiteListFile"
+            fi
+          '';
+          ExecStart = "${sgx-psw}/bin/aesm_service --no-daemon";
+          ExecReload = ''${pkgs.coreutils}/bin/kill -SIGHUP "$MAINPID"'';
+
+          Restart = "on-failure";
+          RestartSec = "15s";
+
+          DynamicUser = true;
+          Group = "sgx";
+          SupplementaryGroups = [
+            config.hardware.cpu.intel.sgx.provision.group
+          ];
+
+          Type = "simple";
+
+          WorkingDirectory = storeAesmFolder;
+          StateDirectory = "aesmd";
+          StateDirectoryMode = "0700";
+          RuntimeDirectory = "aesmd";
+          RuntimeDirectoryMode = "0750";
+
+          # Hardening
+
+          # chroot into the runtime directory
+          RootDirectory = "%t/aesmd";
+          BindReadOnlyPaths = [
+            builtins.storeDir
+            # Hardcoded path AESM_CONFIG_FILE in psw/ae/aesm_service/source/utils/aesm_config.cpp
+            "${configFile}:/etc/aesmd.conf"
+          ];
+          BindPaths = [
+            # Hardcoded path CONFIG_SOCKET_PATH in psw/ae/aesm_service/source/core/ipc/SocketConfig.h
+            "%t/aesmd:/var/run/aesmd"
+            "%S/aesmd:/var/opt/aesmd"
+          ];
+
+          # PrivateDevices=true will mount /dev noexec which breaks AESM
+          PrivateDevices = false;
+          DevicePolicy = "closed";
+          DeviceAllow = [
+            # legacy out-of-tree driver
+            "/dev/isgx rw"
+            # DCAP driver
+            "/dev/sgx rw"
+            # in-tree driver
+            "/dev/sgx_enclave rw"
+            "/dev/sgx_provision rw"
+          ];
+
+          # Requires Internet access for attestation
+          PrivateNetwork = false;
+
+          RestrictAddressFamilies = [
+            # Allocates the socket /var/run/aesmd/aesm.socket
+            "AF_UNIX"
+            # Uses the HTTP protocol to initialize some services
+            "AF_INET"
+            "AF_INET6"
+          ];
+
+          # True breaks stuff
+          MemoryDenyWriteExecute = false;
+
+          # needs the ipc syscall in order to run
+          SystemCallFilter = [
+            "@system-service"
+            "~@aio"
+            "~@chown"
+            "~@clock"
+            "~@cpu-emulation"
+            "~@debug"
+            "~@keyring"
+            "~@memlock"
+            "~@module"
+            "~@mount"
+            "~@privileged"
+            "~@raw-io"
+            "~@reboot"
+            "~@resources"
+            "~@setuid"
+            "~@swap"
+            "~@sync"
+            "~@timer"
+          ];
+          SystemCallArchitectures = "native";
+          SystemCallErrorNumber = "EPERM";
+
+          CapabilityBoundingSet = "";
+          KeyringMode = "private";
+          LockPersonality = true;
+          NoNewPrivileges = true;
+          NotifyAccess = "none";
+          PrivateMounts = true;
+          PrivateTmp = true;
+          PrivateUsers = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          UMask = "0066";
+        };
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
index d82ddb894ea5..5853c5a123c6 100644
--- a/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
+++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -8,6 +8,7 @@ in
     proxy = mkOption {
       type = types.str;
       default = config.services.oauth2_proxy.httpAddress;
+      defaultText = literalExpression "config.services.oauth2_proxy.httpAddress";
       description = ''
         The address of the reverse proxy endpoint for oauth2_proxy
       '';
diff --git a/nixpkgs/nixos/modules/services/security/privacyidea.nix b/nixpkgs/nixos/modules/services/security/privacyidea.nix
index 05f4995cc416..b8e2d9a8b0df 100644
--- a/nixpkgs/nixos/modules/services/security/privacyidea.nix
+++ b/nixpkgs/nixos/modules/services/security/privacyidea.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.privacyidea;
+  opt = options.services.privacyidea;
 
   uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; };
   python = uwsgi.python3;
@@ -112,6 +113,7 @@ in
       encFile = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/enckey";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"'';
         description = ''
           This is used to encrypt the token data and token passwords
         '';
@@ -120,6 +122,7 @@ in
       auditKeyPrivate = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/private.pem";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"'';
         description = ''
           Private Key for signing the audit log.
         '';
@@ -128,6 +131,7 @@ in
       auditKeyPublic = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/public.pem";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"'';
         description = ''
           Public key for checking signatures of the audit log.
         '';
@@ -200,6 +204,7 @@ in
       systemd.services.privacyidea = let
         piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
           uwsgi = {
+            buffer-size = 8192;
             plugins = [ "python3" ];
             pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
             socket = "/run/privacyidea/socket";
diff --git a/nixpkgs/nixos/modules/services/security/step-ca.nix b/nixpkgs/nixos/modules/services/security/step-ca.nix
index 2eccc30e4056..27b2ceed1a43 100644
--- a/nixpkgs/nixos/modules/services/security/step-ca.nix
+++ b/nixpkgs/nixos/modules/services/security/step-ca.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, nixosTests, ... }:
 let
   cfg = config.services.step-ca;
   settingsFormat = (pkgs.formats.json { });
@@ -82,6 +82,8 @@ in
       });
     in
     {
+      passthru.tests.step-ca = nixosTests.step-ca;
+
       assertions =
         [
           {
@@ -119,7 +121,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/nixpkgs/nixos/modules/services/security/tor.nix b/nixpkgs/nixos/modules/services/security/tor.nix
index c94b248d5f10..f3ed1d160eed 100644
--- a/nixpkgs/nixos/modules/services/security/tor.nix
+++ b/nixpkgs/nixos/modules/services/security/tor.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with builtins;
 with lib;
 
 let
   cfg = config.services.tor;
+  opt = options.services.tor;
   stateDir = "/var/lib/tor";
   runDir = "/run/tor";
   descriptionGeneric = option: ''
@@ -799,6 +800,11 @@ in
           options.SOCKSPort = mkOption {
             description = descriptionGeneric "SOCKSPort";
             default = if cfg.settings.HiddenServiceNonAnonymousMode == true then [{port = 0;}] else [];
+            defaultText = literalExpression ''
+              if config.${opt.settings}.HiddenServiceNonAnonymousMode == true
+              then [ { port = 0; } ]
+              else [ ]
+            '';
             example = [{port = 9090;}];
             type = types.listOf (optionSOCKSPort true);
           };
@@ -1012,6 +1018,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;
@@ -1019,6 +1026,7 @@ in
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
+        ProtectProc = "invisible";
         ProtectSystem = "strict";
         RemoveIPC = true;
         RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
diff --git a/nixpkgs/nixos/modules/services/security/torsocks.nix b/nixpkgs/nixos/modules/services/security/torsocks.nix
index 47ac95c4626e..fdd6ac32cc66 100644
--- a/nixpkgs/nixos/modules/services/security/torsocks.nix
+++ b/nixpkgs/nixos/modules/services/security/torsocks.nix
@@ -37,6 +37,7 @@ in
       enable = mkOption {
         type        = types.bool;
         default     = config.services.tor.enable && config.services.tor.client.enable;
+        defaultText = literalExpression "config.services.tor.enable && config.services.tor.client.enable";
         description = ''
           Whether to build <literal>/etc/tor/torsocks.conf</literal>
           containing the specified global torsocks configuration.
diff --git a/nixpkgs/nixos/modules/services/security/vault.nix b/nixpkgs/nixos/modules/services/security/vault.nix
index b0ade62d97c9..d48bc472cb82 100644
--- a/nixpkgs/nixos/modules/services/security/vault.nix
+++ b/nixpkgs/nixos/modules/services/security/vault.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.vault;
+  opt = options.services.vault;
 
   configFile = pkgs.writeText "vault.hcl" ''
     listener "tcp" {
@@ -83,6 +84,11 @@ in
       storagePath = mkOption {
         type = types.nullOr types.path;
         default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
+        defaultText = literalExpression ''
+          if config.${opt.storageBackend} == "file"
+          then "/var/lib/vault"
+          else null
+        '';
         description = "Data directory for file backend";
       };
 
diff --git a/nixpkgs/nixos/modules/services/security/yubikey-agent.nix b/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
index 8a2f98d0412d..8be2457e1e2f 100644
--- a/nixpkgs/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixpkgs/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 = {
 
@@ -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/nixpkgs/nixos/modules/services/torrent/peerflix.nix b/nixpkgs/nixos/modules/services/torrent/peerflix.nix
index 3e5f80960dc7..821c829f6b4a 100644
--- a/nixpkgs/nixos/modules/services/torrent/peerflix.nix
+++ b/nixpkgs/nixos/modules/services/torrent/peerflix.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.peerflix;
+  opt = options.services.peerflix;
 
   configFile = pkgs.writeText "peerflix-config.json" ''
     {
@@ -32,6 +33,7 @@ in {
     downloadDir = mkOption {
       description = "Peerflix temporary download directory.";
       default = "${cfg.stateDir}/torrents";
+      defaultText = literalExpression ''"''${config.${opt.stateDir}}/torrents"'';
       type = types.path;
     };
   };
diff --git a/nixpkgs/nixos/modules/services/torrent/rtorrent.nix b/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
index dd7df623c739..759dcfe2e6c5 100644
--- a/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
+++ b/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
@@ -1,10 +1,11 @@
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 
 with lib;
 
 let
 
   cfg = config.services.rtorrent;
+  opt = options.services.rtorrent;
 
 in {
   options.services.rtorrent = {
@@ -21,6 +22,7 @@ in {
     downloadDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/download";
+      defaultText = literalExpression ''"''${config.${opt.dataDir}}/download"'';
       description = ''
         Where to put downloaded files.
       '';
diff --git a/nixpkgs/nixos/modules/services/torrent/transmission.nix b/nixpkgs/nixos/modules/services/torrent/transmission.nix
index b8cfcf391215..d12d8aa23980 100644
--- a/nixpkgs/nixos/modules/services/torrent/transmission.nix
+++ b/nixpkgs/nixos/modules/services/torrent/transmission.nix
@@ -4,18 +4,24 @@ with lib;
 
 let
   cfg = config.services.transmission;
+  opt = options.services.transmission;
   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,81 +30,170 @@ 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}";
+            defaultText = literalExpression ''"''${config.${opt.home}}/${downloadsDir}"'';
+            description = "Directory where to download torrents.";
+          };
+          options.incomplete-dir = mkOption {
+            type = types.path;
+            default = "${cfg.home}/${incompleteDir}";
+            defaultText = literalExpression ''"''${config.${opt.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 3;
+            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}";
+            defaultText = literalExpression ''"''${config.${opt.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 {
-        type = types.str;
-        default = "770";
-        example = "775";
+        type = with types; nullOr str;
+        default = null;
+        example = "770";
         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>.
+          If not <code>null</code>, is used as the permissions
+          set by <literal>systemd.activationScripts.transmission-daemon</literal>
+          on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>,
+          <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
+          and <xref linkend="opt-services.transmission.settings.watch-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 +214,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'';
@@ -146,43 +252,17 @@ in
     # when /home/foo is not owned by cfg.user.
     # Note also that using an ExecStartPre= wouldn't work either
     # because BindPaths= needs these directories before.
-    system.activationScripts.transmission-daemon = ''
-      install -d -m 700 '${cfg.home}/${settingsDir}'
-      chown -R '${cfg.user}:${cfg.group}' ${cfg.home}/${settingsDir}
-      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
-      '' + optionalString cfg.settings.incomplete-dir-enabled ''
-      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
-      '' + 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; };
+    system.activationScripts = mkIf (cfg.downloadDirPermissions != null)
+      { transmission-daemon = ''
+        install -d -m 700 '${cfg.home}/${settingsDir}'
+        chown -R '${cfg.user}:${cfg.group}' ${cfg.home}/${settingsDir}
+        install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
+        '' + optionalString cfg.settings.incomplete-dir-enabled ''
+        install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
+        '' + optionalString cfg.settings.watch-dir-enabled ''
+        install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
+        '';
+      };
 
     systemd.services.transmission = {
       description = "Transmission BitTorrent Service";
@@ -199,15 +279,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";
@@ -228,11 +306,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.
@@ -241,8 +317,18 @@ 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;
+        StateDirectory = [
+          "transmission"
+          "transmission/.config/transmission-daemon"
+          "transmission/.incomplete"
+          "transmission/Downloads"
+          "transmission/watch-dir"
+        ];
+        StateDirectoryMode = mkDefault 750;
         # The following options are only for optimizing:
         # systemd-analyze security transmission
         AmbientCapabilities = "";
@@ -287,7 +373,6 @@ in
           "quotactl"
         ];
         SystemCallArchitectures = "native";
-        SystemCallErrorNumber = "EPERM";
       };
     };
 
@@ -309,25 +394,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,
@@ -342,21 +430,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;
       })
     ];
 
@@ -372,7 +460,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}/**,
@@ -380,12 +468,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/nixpkgs/nixos/modules/services/ttys/getty.nix b/nixpkgs/nixos/modules/services/ttys/getty.nix
index 8c5b6e5e0cbc..7021a2c80f85 100644
--- a/nixpkgs/nixos/modules/services/ttys/getty.nix
+++ b/nixpkgs/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 = {
@@ -92,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.
-        '';
-      };
-
     };
 
   };
@@ -124,10 +114,9 @@ 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;
       };
diff --git a/nixpkgs/nixos/modules/services/video/epgstation/default.nix b/nixpkgs/nixos/modules/services/video/epgstation/default.nix
index e34b6e0510a5..41613dcbb3ba 100644
--- a/nixpkgs/nixos/modules/services/video/epgstation/default.nix
+++ b/nixpkgs/nixos/modules/services/video/epgstation/default.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.epgstation;
+  opt = options.services.epgstation;
 
   username = config.users.users.epgstation.name;
   groupname = config.users.users.epgstation.group;
@@ -33,7 +34,7 @@ let
     fi
   '';
 
-  streamingConfig = builtins.fromJSON (builtins.readFile ./streaming.json);
+  streamingConfig = lib.importJSON ./streaming.json;
   logConfig = {
     appenders.stdout.type = "stdout";
     categories = {
@@ -48,7 +49,7 @@ let
 in
 {
   options.services.epgstation = {
-    enable = mkEnableOption pkgs.epgstation.meta.description;
+    enable = mkEnableOption "EPGStation: DTV Software in Japan";
 
     usePreconfiguredStreaming = mkOption {
       type = types.bool;
@@ -72,6 +73,7 @@ in
     socketioPort = mkOption {
       type = types.port;
       default = cfg.port + 1;
+      defaultText = literalExpression "config.${opt.port} + 1";
       description = ''
         Socket.io port for EPGStation to listen on.
       '';
@@ -80,6 +82,7 @@ in
     clientSocketioPort = mkOption {
       type = types.port;
       default = cfg.socketioPort;
+      defaultText = literalExpression "config.${opt.socketioPort}";
       description = ''
         Socket.io port that the web client is going to connect to. This may be
         different from <option>socketioPort</option> if EPGStation is hidden
@@ -183,6 +186,9 @@ in
         in {
           type = types.str;
           default = "http+unix://${replaceStrings ["/"] ["%2F"] sockPath}";
+          defaultText = literalExpression ''
+            "http+unix://''${replaceStrings ["/"] ["%2F"] config.${options.services.mirakurun.unixSocket}}"
+          '';
           example = "http://localhost:40772";
           description = "URL to connect to Mirakurun.";
         });
diff --git a/nixpkgs/nixos/modules/services/video/mirakurun.nix b/nixpkgs/nixos/modules/services/video/mirakurun.nix
index 16efb56cfd61..35303b2332c6 100644
--- a/nixpkgs/nixos/modules/services/video/mirakurun.nix
+++ b/nixpkgs/nixos/modules/services/video/mirakurun.nix
@@ -24,7 +24,7 @@ in
   {
     options = {
       services.mirakurun = {
-        enable = mkEnableOption mirakurun.meta.description;
+        enable = mkEnableOption "the Mirakurun DVR Tuner Server";
 
         port = mkOption {
           type = with types; nullOr port;
diff --git a/nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix b/nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix
new file mode 100644
index 000000000000..644b1945a1ec
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/video/rtsp-simple-server.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rtsp-simple-server;
+  package = pkgs.rtsp-simple-server;
+  format = pkgs.formats.yaml {};
+in
+{
+  options = {
+    services.rtsp-simple-server = {
+      enable = mkEnableOption "RTSP Simple Server";
+
+      settings = mkOption {
+        description = ''
+          Settings for rtsp-simple-server.
+          Read more at <link xlink:href="https://github.com/aler9/rtsp-simple-server/blob/main/rtsp-simple-server.yml"/>
+        '';
+        type = format.type;
+
+        default = {
+          logLevel = "info";
+          logDestinations = [
+            "stdout"
+          ];
+          # we set this so when the user uses it, it just works (see LogsDirectory below). but it's not used by default.
+          logFile = "/var/log/rtsp-simple-server/rtsp-simple-server.log";
+        };
+
+        example = {
+          paths = {
+            cam = {
+              runOnInit = "ffmpeg -f v4l2 -i /dev/video0 -f rtsp rtsp://localhost:$RTSP_PORT/$RTSP_PATH";
+              runOnInitRestart = true;
+            };
+          };
+        };
+      };
+
+      env = mkOption {
+        type = with types; attrsOf anything;
+        description = "Extra environment variables for RTSP Simple Server";
+        default = {};
+        example = {
+          RTSP_CONFKEY = "mykey";
+        };
+      };
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    # NOTE: rtsp-simple-server watches this file and automatically reloads if it changes
+    environment.etc."rtsp-simple-server.yaml".source = format.generate "rtsp-simple-server.yaml" cfg.settings;
+
+    systemd.services.rtsp-simple-server = {
+      environment = cfg.env;
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = with pkgs; [
+        ffmpeg
+      ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        User = "rtsp-simple-server";
+        Group = "rtsp-simple-server";
+
+        LogsDirectory = "rtsp-simple-server";
+
+        # user likely may want to stream cameras, can't hurt to add video group
+        SupplementaryGroups = "video";
+
+        ExecStart = "${package}/bin/rtsp-simple-server /etc/rtsp-simple-server.yaml";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/video/unifi-video.nix b/nixpkgs/nixos/modules/services/video/unifi-video.nix
index 17971b23db82..43208a9fe4cf 100644
--- a/nixpkgs/nixos/modules/services/video/unifi-video.nix
+++ b/nixpkgs/nixos/modules/services/video/unifi-video.nix
@@ -1,7 +1,8 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, options, pkgs, utils, ... }:
 with lib;
 let
   cfg = config.services.unifi-video;
+  opt = options.services.unifi-video;
   mainClass = "com.ubnt.airvision.Main";
   cmd = ''
     ${pkgs.jsvc}/bin/jsvc \
@@ -164,6 +165,7 @@ in
       pidFile = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/unifi-video.pid";
+        defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
         description = "Location of unifi-video pid file.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/wayland/cage.nix b/nixpkgs/nixos/modules/services/wayland/cage.nix
index 273693a3b2fe..d2bbc4fc057b 100644
--- a/nixpkgs/nixos/modules/services/wayland/cage.nix
+++ b/nixpkgs/nixos/modules/services/wayland/cage.nix
@@ -74,6 +74,8 @@ in {
         TTYVTDisallocate = "yes";
         # Fail to start if not controlling the virtual terminal.
         StandardInput = "tty-fail";
+        StandardOutput = "journal";
+        StandardError = "journal";
         # Set up a full (custom) user session for the user, required by Cage.
         PAMName = "cage";
       };
diff --git a/nixpkgs/nixos/modules/services/web-apps/bookstack.nix b/nixpkgs/nixos/modules/services/web-apps/bookstack.nix
index 74eeb2faa4a3..54c491f8b176 100644
--- a/nixpkgs/nixos/modules/services/web-apps/bookstack.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/bookstack.nix
@@ -221,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/nixpkgs/nixos/modules/services/web-apps/code-server.nix b/nixpkgs/nixos/modules/services/web-apps/code-server.nix
new file mode 100644
index 000000000000..474e9140ae87
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/web-apps/discourse.nix b/nixpkgs/nixos/modules/services/web-apps/discourse.nix
index c4fb7e2b316f..2c2911aada3f 100644
--- a/nixpkgs/nixos/modules/services/web-apps/discourse.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/discourse.nix
@@ -4,6 +4,7 @@ let
   json = pkgs.formats.json {};
 
   cfg = config.services.discourse;
+  opt = options.services.discourse;
 
   # Keep in sync with https://github.com/discourse/discourse_docker/blob/master/image/base/Dockerfile#L5
   upstreamPostgresqlVersion = lib.getVersion pkgs.postgresql_13;
@@ -327,6 +328,7 @@ in
         useSSL = lib.mkOption {
           type = lib.types.bool;
           default = cfg.redis.host != "localhost";
+          defaultText = lib.literalExpression ''config.${opt.redis.host} != "localhost"'';
           description = ''
             Connect to Redis with SSL.
           '';
@@ -399,6 +401,7 @@ in
           domain = lib.mkOption {
             type = lib.types.str;
             default = cfg.hostname;
+            defaultText = lib.literalExpression "config.${opt.hostname}";
             description = ''
               HELO domain to use for outgoing mail.
             '';
@@ -621,12 +624,13 @@ in
 
       max_user_api_reqs_per_minute = 20;
       max_user_api_reqs_per_day = 2880;
-      max_admin_api_reqs_per_key_per_minute = 60;
+      max_admin_api_reqs_per_minute = 60;
       max_reqs_per_ip_per_minute = 200;
       max_reqs_per_ip_per_10_seconds = 50;
       max_asset_reqs_per_ip_per_10_seconds = 200;
       max_reqs_per_ip_mode = "block";
       max_reqs_rate_limit_on_private = false;
+      skip_per_ip_rate_limit_trust_level = 1;
       force_anonymous_min_queue_seconds = 1;
       force_anonymous_min_per_10_seconds = 3;
       background_requests_max_queue_length = 0.5;
@@ -646,6 +650,9 @@ in
       enable_email_sync_demon = false;
       max_digests_enqueued_per_30_mins_per_site = 10000;
       cluster_name = null;
+      multisite_config_path = "config/multisite.yml";
+      enable_long_polling = null;
+      long_polling_interval = null;
     };
 
     services.redis.enable = lib.mkDefault (cfg.redis.host == "localhost");
@@ -825,7 +832,7 @@ in
 
       appendHttpConfig = ''
         # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week)
-        # levels means it is a 2 deep heirarchy cause we can have lots of files
+        # levels means it is a 2 deep hierarchy cause we can have lots of files
         # max_size limits the size of the cache
         proxy_cache_path /var/cache/nginx inactive=1440m levels=1:2 keys_zone=discourse:10m max_size=600m;
 
@@ -837,7 +844,7 @@ in
         inherit (cfg) sslCertificate sslCertificateKey enableACME;
         forceSSL = lib.mkDefault tlsEnabled;
 
-        root = "/run/discourse/public";
+        root = "${cfg.package}/share/discourse/public";
 
         locations =
           let
@@ -889,7 +896,7 @@ in
               "~ ^/uploads/" = proxy {
                 extraConfig = cache_1y + ''
                   proxy_set_header X-Sendfile-Type X-Accel-Redirect;
-                  proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/;
+                  proxy_set_header X-Accel-Mapping ${cfg.package}/share/discourse/public/=/downloads/;
 
                   # custom CSS
                   location ~ /stylesheet-cache/ {
@@ -911,7 +918,7 @@ in
               "~ ^/admin/backups/" = proxy {
                 extraConfig = ''
                   proxy_set_header X-Sendfile-Type X-Accel-Redirect;
-                  proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/;
+                  proxy_set_header X-Accel-Mapping ${cfg.package}/share/discourse/public/=/downloads/;
                 '';
               };
               "~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker)" = proxy {
@@ -938,7 +945,7 @@ in
               };
               "/downloads/".extraConfig = ''
                 internal;
-                alias /run/discourse/public/;
+                alias ${cfg.package}/share/discourse/public/;
               '';
             };
       };
diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.xml b/nixpkgs/nixos/modules/services/web-apps/discourse.xml
index 184c9c6363e5..ad9b65abf51e 100644
--- a/nixpkgs/nixos/modules/services/web-apps/discourse.xml
+++ b/nixpkgs/nixos/modules/services/web-apps/discourse.xml
@@ -25,7 +25,7 @@ services.discourse = {
   };
   <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file";
 };
-<link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com";
+<link linkend="opt-security.acme.defaults.email">security.acme.email</link> = "me@example.com";
 <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
 </programlisting>
    </para>
@@ -297,7 +297,7 @@ services.discourse = {
       the script:
       <programlisting language="bash">
 ./update.py update-plugins
-</programlisting>.
+</programlisting>
     </para>
 
     <para>
diff --git a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
index bc5b1a8be545..9b9ae931f9a7 100644
--- a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
@@ -66,6 +66,8 @@ let
   siteOpts = { config, lib, name, ... }:
     {
       options = {
+        enable = mkEnableOption "DokuWiki web application.";
+
         package = mkOption {
           type = types.package;
           default = pkgs.dokuwiki;
@@ -306,6 +308,9 @@ in
         inherit user;
         group = webserver.group;
 
+        # Not yet compatible with php 8 https://www.dokuwiki.org/requirements
+        # https://github.com/splitbrain/dokuwiki/issues/3545
+        phpPackage = pkgs.php74;
         phpEnv = {
           DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}";
           DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}";
@@ -444,5 +449,6 @@ in
   meta.maintainers = with maintainers; [
     _1000101
     onny
+    dandellion
   ];
 }
diff --git a/nixpkgs/nixos/modules/services/web-apps/galene.nix b/nixpkgs/nixos/modules/services/web-apps/galene.nix
index db9dfeb47499..1d0a620585b0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/galene.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/galene.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 let
   cfg = config.services.galene;
+  opt = options.services.galene;
   defaultstateDir = "/var/lib/galene";
   defaultrecordingsDir = "${cfg.stateDir}/recordings";
   defaultgroupsDir = "${cfg.stateDir}/groups";
@@ -88,6 +89,7 @@ in
       recordingsDir = mkOption {
         type = types.str;
         default = defaultrecordingsDir;
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/recordings"'';
         example = "/var/lib/galene/recordings";
         description = "Recordings directory.";
       };
@@ -95,6 +97,7 @@ in
       dataDir = mkOption {
         type = types.str;
         default = defaultdataDir;
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/data"'';
         example = "/var/lib/galene/data";
         description = "Data directory.";
       };
@@ -102,6 +105,7 @@ in
       groupsDir = mkOption {
         type = types.str;
         default = defaultgroupsDir;
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/groups"'';
         example = "/var/lib/galene/groups";
         description = "Web server directory.";
       };
diff --git a/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix b/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
index b434f16e9bdc..9eeabb9d5662 100644
--- a/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/hedgedoc.nix
@@ -33,7 +33,7 @@ in
       type = types.listOf types.str;
       default = [];
       description = ''
-        Groups to which the user ${name} should be added.
+        Groups to which the service user should be added.
       '';
     };
 
@@ -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 = ''
diff --git a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix
index 9c66589dffd1..4f6a34e6d2fe 100644
--- a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix
@@ -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/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix
index b4987fa4702c..ad314c885ba8 100644
--- a/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix
+++ b/nixpkgs/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}
   '';
@@ -76,12 +79,24 @@ in
         email = mkOption {
           type = types.str;
           default = "ihatemoney@${config.networking.hostName}";
+          defaultText = literalExpression ''"ihatemoney@''${config.networking.hostName}"'';
           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/nixpkgs/nixos/modules/services/web-apps/invidious.nix b/nixpkgs/nixos/modules/services/web-apps/invidious.nix
new file mode 100644
index 000000000000..10b30bf1fd1d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/invidious.nix
@@ -0,0 +1,264 @@
+{ 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 = [ "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;
+        defaultText = lib.literalExpression "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/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix
index 2eacd87ae6fd..2f1c4acec1e8 100644
--- a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -38,6 +38,10 @@ let
     };
     bosh = "//${cfg.hostName}/http-bind";
     websocket = "wss://${cfg.hostName}/xmpp-websocket";
+
+    fileRecordingsEnabled = true;
+    liveStreamingEnabled = true;
+    hiddenDomain = "recorder.${cfg.hostName}";
   };
 in
 {
@@ -48,7 +52,7 @@ in
       type = str;
       example = "meet.example.org";
       description = ''
-        Hostname of the Jitsi Meet instance.
+        FQDN of the Jitsi Meet instance.
       '';
     };
 
@@ -130,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;
@@ -229,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" ];
@@ -243,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
       ''
@@ -267,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
@@ -380,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/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml
index 97373bc6d9a8..ff44c724adf4 100644
--- a/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml
+++ b/nixpkgs/nixos/modules/services/web-apps/jitsi-meet.xml
@@ -20,7 +20,7 @@
   };
   <link linkend="opt-services.jitsi-videobridge.openFirewall">services.jitsi-videobridge.openFirewall</link> = true;
   <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
-  <link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com";
+  <link linkend="opt-security.acme.defaults.email">security.acme.email</link> = "me@example.com";
   <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
 }</programlisting>
    </para>
@@ -46,7 +46,7 @@
   };
   <link linkend="opt-services.jitsi-videobridge.openFirewall">services.jitsi-videobridge.openFirewall</link> = true;
   <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
-  <link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com";
+  <link linkend="opt-security.acme.defaults.email">security.acme.email</link> = "me@example.com";
   <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true;
 }</programlisting>
    </para>
diff --git a/nixpkgs/nixos/modules/services/web-apps/keycloak.nix b/nixpkgs/nixos/modules/services/web-apps/keycloak.nix
index df8c7114102f..e08f6dcabd2f 100644
--- a/nixpkgs/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/keycloak.nix
@@ -1,7 +1,8 @@
-{ config, pkgs, lib, ... }:
+{ config, options, pkgs, lib, ... }:
 
 let
   cfg = config.services.keycloak;
+  opt = options.services.keycloak;
 in
 {
   options.services.keycloak = {
@@ -139,6 +140,7 @@ in
           lib.mkOption {
             type = lib.types.port;
             default = dbPorts.${cfg.database.type};
+            defaultText = lib.literalDocBook "default port of selected database";
             description = ''
               Port of the database to connect to.
             '';
@@ -147,6 +149,7 @@ in
       useSSL = lib.mkOption {
         type = lib.types.bool;
         default = cfg.database.host != "localhost";
+        defaultText = lib.literalExpression ''config.${opt.database.host} != "localhost"'';
         description = ''
           Whether the database connection should be secured by SSL /
           TLS.
diff --git a/nixpkgs/nixos/modules/services/web-apps/lemmy.nix b/nixpkgs/nixos/modules/services/web-apps/lemmy.nix
index ae7d0d02c894..7cd2357c4556 100644
--- a/nixpkgs/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/lemmy.nix
@@ -214,8 +214,6 @@ in
       systemd.services.lemmy-postgresql = mkIf cfg.settings.database.createLocally {
         description = "Lemmy postgresql db";
         after = [ "postgresql.service" ];
-        bindsTo = [ "postgresql.service" ];
-        requiredBy = [ "lemmy.service" ];
         partOf = [ "lemmy.service" ];
         script = with cfg.settings.database; ''
           PSQL() {
diff --git a/nixpkgs/nixos/modules/services/web-apps/mastodon.nix b/nixpkgs/nixos/modules/services/web-apps/mastodon.nix
index d3790d8b1760..1e3c7e53c175 100644
--- a/nixpkgs/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/mastodon.nix
@@ -38,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
@@ -50,6 +50,9 @@ let
     # Logs directory and mode
     LogsDirectory = "mastodon";
     LogsDirectoryMode = "0750";
+    # Proc filesystem
+    ProcSubset = "pid";
+    ProtectProc = "invisible";
     # Access write directories
     UMask = "0027";
     # Capabilities
@@ -74,6 +77,7 @@ let
     MemoryDenyWriteExecute = false;
     RestrictRealtime = true;
     RestrictSUIDSGID = true;
+    RemoveIPC = true;
     PrivateMounts = true;
     # System Call Filtering
     SystemCallArchitectures = "native";
@@ -344,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 {
@@ -464,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" ];
@@ -491,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" ];
@@ -517,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;
     };
 
@@ -541,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 ];
     };
@@ -563,7 +567,7 @@ in {
         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 ];
     };
@@ -596,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/nixpkgs/nixos/modules/services/web-apps/matomo.nix b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
index b0d281cfb6ed..8a0ca33b51f0 100644
--- a/nixpkgs/nixos/modules/services/web-apps/matomo.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 with lib;
 let
   cfg = config.services.matomo;
@@ -12,10 +12,7 @@ let
   phpExecutionUnit = "phpfpm-${pool}";
   databaseService = "mysql.service";
 
-  fqdn =
-    let
-      join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
-     in join config.networking.hostName config.networking.domain;
+  fqdn = if config.networking.domain != null then config.networking.fqdn else config.networking.hostName;
 
 in {
   imports = [
@@ -24,6 +21,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 = {
@@ -77,12 +75,17 @@ in {
         '';
       };
 
-      periodicArchiveProcessingUrl = mkOption {
+      hostname = mkOption {
         type = types.str;
         default = "${user}.${fqdn}";
+        defaultText = literalExpression ''
+          if config.${options.networking.domain} != null
+          then "${user}.''${config.${options.networking.fqdn}}"
+          else "${user}.''${config.${options.networking.hostName}}"
+        '';
         example = "matomo.yourdomain.org";
         description = ''
-          URL of the host, without https prefix. By default, this is ${user}.${fqdn}, but you may want to change it if you
+          URL of the host, without https prefix. You may want to change it if you
           run Matomo on a different URL than matomo.yourdomain.
         '';
       };
@@ -170,6 +173,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
@@ -202,7 +218,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}";
       };
     };
 
@@ -258,7 +274,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/nixpkgs/nixos/modules/services/web-apps/mattermost.nix b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
index f5c2c356afce..310a673f5114 100644
--- a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
@@ -6,23 +6,95 @@ let
 
   cfg = config.services.mattermost;
 
-  defaultConfig = builtins.fromJSON (builtins.replaceStrings [ "\\u0026" ] [ "&" ]
-    (readFile "${pkgs.mattermost}/config/config.json")
-  );
-
   database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10";
 
-  mattermostConf = foldl recursiveUpdate defaultConfig
-    [ { ServiceSettings.SiteURL = cfg.siteUrl;
-        ServiceSettings.ListenAddress = cfg.listenAddress;
-        TeamSettings.SiteName = cfg.siteName;
-        SqlSettings.DriverName = "postgres";
-        SqlSettings.DataSource = database;
+  postgresPackage = config.services.postgresql.package;
+
+  createDb = {
+    statePath ? cfg.statePath,
+    localDatabaseUser ? cfg.localDatabaseUser,
+    localDatabasePassword ? cfg.localDatabasePassword,
+    localDatabaseName ? cfg.localDatabaseName,
+    useSudo ? true
+  }: ''
+    if ! test -e ${escapeShellArg "${statePath}/.db-created"}; then
+      ${lib.optionalString useSudo "${pkgs.sudo}/bin/sudo -u ${escapeShellArg config.services.postgresql.superUser} \\"}
+        ${postgresPackage}/bin/psql postgres -c \
+          "CREATE ROLE ${localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${localDatabasePassword}'"
+      ${lib.optionalString useSudo "${pkgs.sudo}/bin/sudo -u ${escapeShellArg config.services.postgresql.superUser} \\"}
+        ${postgresPackage}/bin/createdb \
+          --owner ${escapeShellArg localDatabaseUser} ${escapeShellArg localDatabaseName}
+      touch ${escapeShellArg "${statePath}/.db-created"}
+    fi
+  '';
+
+  mattermostPluginDerivations = with pkgs;
+    map (plugin: stdenv.mkDerivation {
+      name = "mattermost-plugin";
+      installPhase = ''
+        mkdir -p $out/share
+        cp ${plugin} $out/share/plugin.tar.gz
+      '';
+      dontUnpack = true;
+      dontPatch = true;
+      dontConfigure = true;
+      dontBuild = true;
+      preferLocalBuild = true;
+    }) cfg.plugins;
+
+  mattermostPlugins = with pkgs;
+    if mattermostPluginDerivations == [] then null
+    else stdenv.mkDerivation {
+      name = "${cfg.package.name}-plugins";
+      nativeBuildInputs = [
+        autoPatchelfHook
+      ] ++ mattermostPluginDerivations;
+      buildInputs = [
+        cfg.package
+      ];
+      installPhase = ''
+        mkdir -p $out/data/plugins
+        plugins=(${escapeShellArgs (map (plugin: "${plugin}/share/plugin.tar.gz") mattermostPluginDerivations)})
+        for plugin in "''${plugins[@]}"; do
+          hash="$(sha256sum "$plugin" | cut -d' ' -f1)"
+          mkdir -p "$hash"
+          tar -C "$hash" -xzf "$plugin"
+          autoPatchelf "$hash"
+          GZIP_OPT=-9 tar -C "$hash" -cvzf "$out/data/plugins/$hash.tar.gz" .
+          rm -rf "$hash"
+        done
+      '';
+
+      dontUnpack = true;
+      dontPatch = true;
+      dontConfigure = true;
+      dontBuild = true;
+      preferLocalBuild = true;
+    };
+
+  mattermostConfWithoutPlugins = recursiveUpdate
+    { ServiceSettings.SiteURL = cfg.siteUrl;
+      ServiceSettings.ListenAddress = cfg.listenAddress;
+      TeamSettings.SiteName = cfg.siteName;
+      SqlSettings.DriverName = "postgres";
+      SqlSettings.DataSource = database;
+      PluginSettings.Directory = "${cfg.statePath}/plugins/server";
+      PluginSettings.ClientDirectory = "${cfg.statePath}/plugins/client";
+    }
+    cfg.extraConfig;
+
+  mattermostConf = recursiveUpdate
+    mattermostConfWithoutPlugins
+    (
+      if mattermostPlugins == null then {}
+      else {
+        PluginSettings = {
+          Enable = true;
+        };
       }
-      cfg.extraConfig
-    ];
+    );
 
-  mattermostConfJSON = pkgs.writeText "mattermost-config-raw.json" (builtins.toJSON mattermostConf);
+  mattermostConfJSON = pkgs.writeText "mattermost-config.json" (builtins.toJSON mattermostConf);
 
 in
 
@@ -31,6 +103,13 @@ in
     services.mattermost = {
       enable = mkEnableOption "Mattermost chat server";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mattermost;
+        defaultText = "pkgs.mattermost";
+        description = "Mattermost derivation to use.";
+      };
+
       statePath = mkOption {
         type = types.str;
         default = "/var/lib/mattermost";
@@ -77,6 +156,16 @@ in
         '';
       };
 
+      preferNixConfig = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If both mutableConfig and this option are set, the Nix configuration
+          will take precedence over any settings configured in the server
+          console.
+        '';
+      };
+
       extraConfig = mkOption {
         type = types.attrs;
         default = { };
@@ -85,6 +174,17 @@ in
         '';
       };
 
+      plugins = mkOption {
+        type = types.listOf (types.oneOf [types.path types.package]);
+        default = [];
+        example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]";
+        description = ''
+          Plugins to add to the configuration. Overrides any installed if non-null.
+          This is a list of paths to .tar.gz files or derivations evaluating to
+          .tar.gz files. All entries will be passed to `mattermost plugin add`.
+        '';
+      };
+
       localDatabaseCreate = mkOption {
         type = types.bool;
         default = true;
@@ -135,6 +235,12 @@ in
 
       matterircd = {
         enable = mkEnableOption "Mattermost IRC bridge";
+        package = mkOption {
+          type = types.package;
+          default = pkgs.matterircd;
+          defaultText = "pkgs.matterircd";
+          description = "matterircd derivation to use.";
+        };
         parameters = mkOption {
           type = types.listOf types.str;
           default = [ ];
@@ -167,7 +273,7 @@ in
       # The systemd service will fail to execute the preStart hook
       # if the WorkingDirectory does not exist
       system.activationScripts.mattermost = ''
-        mkdir -p ${cfg.statePath}
+        mkdir -p "${cfg.statePath}"
       '';
 
       systemd.services.mattermost = {
@@ -176,39 +282,41 @@ in
         after = [ "network.target" "postgresql.service" ];
 
         preStart = ''
-          mkdir -p ${cfg.statePath}/{data,config,logs}
-          ln -sf ${pkgs.mattermost}/{bin,fonts,i18n,templates,client} ${cfg.statePath}
+          mkdir -p "${cfg.statePath}"/{data,config,logs,plugins}
+          mkdir -p "${cfg.statePath}/plugins"/{client,server}
+          ln -sf ${cfg.package}/{bin,fonts,i18n,templates,client} "${cfg.statePath}"
+        '' + lib.optionalString (mattermostPlugins != null) ''
+          rm -rf "${cfg.statePath}/data/plugins"
+          ln -sf ${mattermostPlugins}/data/plugins "${cfg.statePath}/data"
         '' + lib.optionalString (!cfg.mutableConfig) ''
-          rm -f ${cfg.statePath}/config/config.json
-          cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
-          ${pkgs.mattermost}/bin/mattermost config migrate ${cfg.statePath}/config/config.json ${database}
+          rm -f "${cfg.statePath}/config/config.json"
+          ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json"
         '' + lib.optionalString cfg.mutableConfig ''
           if ! test -e "${cfg.statePath}/config/.initial-created"; then
             rm -f ${cfg.statePath}/config/config.json
-            cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
-            touch ${cfg.statePath}/config/.initial-created
+            ${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${cfg.package}/config/config.json ${mattermostConfJSON} > "${cfg.statePath}/config/config.json"
+            touch "${cfg.statePath}/config/.initial-created"
           fi
-        '' + lib.optionalString cfg.localDatabaseCreate ''
-          if ! test -e "${cfg.statePath}/.db-created"; then
-            ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
-              ${config.services.postgresql.package}/bin/psql postgres -c \
-                "CREATE ROLE ${cfg.localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.localDatabasePassword}'"
-            ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
-              ${config.services.postgresql.package}/bin/createdb \
-                --owner ${cfg.localDatabaseUser} ${cfg.localDatabaseName}
-            touch ${cfg.statePath}/.db-created
-          fi
-        '' + ''
-          chown ${cfg.user}:${cfg.group} -R ${cfg.statePath}
-          chmod u+rw,g+r,o-rwx -R ${cfg.statePath}
+        '' + lib.optionalString (cfg.mutableConfig && cfg.preferNixConfig) ''
+          new_config="$(${pkgs.jq}/bin/jq -s '.[0] * .[1]' "${cfg.statePath}/config/config.json" ${mattermostConfJSON})"
+
+          rm -f "${cfg.statePath}/config/config.json"
+          echo "$new_config" > "${cfg.statePath}/config/config.json"
+        '' + lib.optionalString cfg.localDatabaseCreate (createDb {}) + ''
+          # Don't change permissions recursively on the data, current, and symlinked directories (see ln -sf command above).
+          # This dramatically decreases startup times for installations with a lot of files.
+          find . -maxdepth 1 -not -name data -not -name client -not -name templates -not -name i18n -not -name fonts -not -name bin -not -name . \
+            -exec chown "${cfg.user}:${cfg.group}" -R {} \; -exec chmod u+rw,g+r,o-rwx -R {} \;
+
+          chown "${cfg.user}:${cfg.group}" "${cfg.statePath}/data" .
+          chmod u+rw,g+r,o-rwx "${cfg.statePath}/data" .
         '';
 
         serviceConfig = {
           PermissionsStartOnly = true;
           User = cfg.user;
           Group = cfg.group;
-          ExecStart = "${pkgs.mattermost}/bin/mattermost" +
-            (lib.optionalString (!cfg.mutableConfig) " -c ${database}");
+          ExecStart = "${cfg.package}/bin/mattermost";
           WorkingDirectory = "${cfg.statePath}";
           Restart = "always";
           RestartSec = "10";
@@ -224,7 +332,7 @@ in
         serviceConfig = {
           User = "nobody";
           Group = "nogroup";
-          ExecStart = "${pkgs.matterircd}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}";
+          ExecStart = "${cfg.matterircd.package}/bin/matterircd ${escapeShellArgs cfg.matterircd.parameters}";
           WorkingDirectory = "/tmp";
           PrivateTmp = true;
           Restart = "always";
diff --git a/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix b/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix
deleted file mode 100644
index efb73124a237..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix
+++ /dev/null
@@ -1,304 +0,0 @@
-{ config, lib, pkgs, ... }:
-with lib;
-
-let
-  cfg = config.services.moinmoin;
-  python = pkgs.python27;
-  pkg = python.pkgs.moinmoin;
-  dataDir = "/var/lib/moin";
-  usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn";
-  usingNginx = cfg.webServer == "nginx-gunicorn";
-  user = "moin";
-  group = "moin";
-
-  uLit = s: ''u"${s}"'';
-  indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str);
-
-  moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" ''
-    ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user}
-  '';
-
-  wikiConfig = wikiIdent: w: ''
-    # -*- coding: utf-8 -*-
-
-    from MoinMoin.config import multiconfig, url_prefix_static
-
-    class Config(multiconfig.DefaultConfig):
-        ${optionalString (w.webLocation != "/") ''
-          url_prefix_static = '${w.webLocation}' + url_prefix_static
-        ''}
-
-        sitename = u'${w.siteName}'
-        page_front_page = u'${w.frontPage}'
-
-        data_dir = '${dataDir}/${wikiIdent}/data'
-        data_underlay_dir = '${dataDir}/${wikiIdent}/underlay'
-
-        language_default = u'${w.languageDefault}'
-        ${optionalString (w.superUsers != []) ''
-          superuser = [${concatMapStringsSep ", " uLit w.superUsers}]
-        ''}
-
-    ${indentLines 4 w.extraConfig}
-  '';
-  wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki);
-
-in
-{
-  options.services.moinmoin = with types; {
-    enable = mkEnableOption "MoinMoin Wiki Engine";
-
-    webServer = mkOption {
-      type = enum [ "nginx-gunicorn" "gunicorn" "none" ];
-      default = "nginx-gunicorn";
-      example = "none";
-      description = ''
-        Which web server to use to serve the wiki.
-        Use <literal>none</literal> if you want to configure this yourself.
-      '';
-    };
-
-    gunicorn.workers = mkOption {
-      type = ints.positive;
-      default = 3;
-      example = 10;
-      description = ''
-        The number of worker processes for handling requests.
-      '';
-    };
-
-    wikis = mkOption {
-      type = attrsOf (submodule ({ name, ... }: {
-        options = {
-          siteName = mkOption {
-            type = str;
-            default = "Untitled Wiki";
-            example = "ExampleWiki";
-            description = ''
-              Short description of your wiki site, displayed below the logo on each page, and
-              used in RSS documents as the channel title.
-            '';
-          };
-
-          webHost = mkOption {
-            type = str;
-            description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used.";
-            example = "wiki.example.org";
-          };
-
-          webLocation = mkOption {
-            type = str;
-            default = "/";
-            example = "/moin";
-            description = "Location part of the wiki URL.";
-          };
-
-          frontPage = mkOption {
-            type = str;
-            default = "LanguageSetup";
-            example = "FrontPage";
-            description = ''
-              Front page name. Set this to something like <literal>FrontPage</literal> once languages are
-              configured.
-            '';
-          };
-
-          superUsers = mkOption {
-            type = listOf str;
-            default = [];
-            example = [ "elvis" ];
-            description = ''
-              List of trusted user names with wiki system administration super powers.
-
-              Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.:
-              <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>.
-            '';
-          };
-
-          languageDefault = mkOption {
-            type = str;
-            default = "en";
-            example = "de";
-            description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored.";
-          };
-
-          extraConfig = mkOption {
-            type = lines;
-            default = "";
-            example = ''
-              show_hosts = True
-              search_results_per_page = 100
-              acl_rights_default = u"Known:read,write,delete,revert All:read"
-              logo_string = u"<h2>\U0001f639</h2>"
-              theme_default = u"modernized"
-
-              user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0}
-              navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar
-              actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount']
-
-              mail_smarthost = "mail.example.org"
-              mail_from = u"Example.Org Wiki <wiki@example.org>"
-            '';
-            description = ''
-              Additional configuration to be appended verbatim to this wiki's config.
-
-              See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation.
-            '';
-          };
-
-        };
-        config = {
-          webHost = mkDefault name;
-        };
-      }));
-      example = literalExpression ''
-        {
-          "mywiki" = {
-            siteName = "Example Wiki";
-            webHost = "wiki.example.org";
-            superUsers = [ "admin" ];
-            frontPage = "Index";
-            extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'"
-          };
-        }
-      '';
-      description = ''
-        Configurations of the individual wikis. Attribute names must be valid Python
-        identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>.
-
-        For every attribute <replaceable>WIKINAME</replaceable>, a helper script
-        moin-<replaceable>WIKINAME</replaceable> is created which runs the
-        <command>moin</command> command under the <literal>moin</literal> user (to avoid
-        file ownership issues) and with the right configuration directory passed to it.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    assertions = forEach (attrNames cfg.wikis) (wname:
-      { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null;
-        message = "${wname} is not valid Python identifier";
-      }
-    );
-
-    users.users = {
-      moin = {
-        description = "MoinMoin wiki";
-        home = dataDir;
-        group = group;
-        isSystemUser = true;
-      };
-    };
-
-    users.groups = {
-      moin = {
-        members = mkIf usingNginx [ config.services.nginx.user ];
-      };
-    };
-
-    environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis);
-
-    systemd.services = mkIf usingGunicorn
-      (flip mapAttrs' cfg.wikis (wikiIdent: wiki:
-        nameValuePair "moin-${wikiIdent}"
-          {
-            description = "MoinMoin wiki ${wikiIdent} - gunicorn process";
-            wantedBy = [ "multi-user.target" ];
-            after = [ "network.target" ];
-            restartIfChanged = true;
-            restartTriggers = [ (wikiConfigFile wikiIdent wiki) ];
-
-            environment = let
-              penv = python.buildEnv.override {
-                # setuptools: https://github.com/benoitc/gunicorn/issues/1716
-                extraLibs = [ python.pkgs.eventlet python.pkgs.setuptools pkg ];
-              };
-            in {
-              PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
-            };
-
-            preStart = ''
-              umask 0007
-              rm -rf ${dataDir}/${wikiIdent}/underlay
-              cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/
-              chmod -R u+w ${dataDir}/${wikiIdent}/underlay
-            '';
-
-            startLimitIntervalSec = 30;
-
-            serviceConfig = {
-              User = user;
-              Group = group;
-              WorkingDirectory = "${dataDir}/${wikiIdent}";
-              ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
-                --name gunicorn-${wikiIdent} \
-                --workers ${toString cfg.gunicorn.workers} \
-                --worker-class eventlet \
-                --bind unix:/run/moin/${wikiIdent}/gunicorn.sock
-              '';
-
-              Restart = "on-failure";
-              RestartSec = "2s";
-
-              StateDirectory = "moin/${wikiIdent}";
-              StateDirectoryMode = "0750";
-              RuntimeDirectory = "moin/${wikiIdent}";
-              RuntimeDirectoryMode = "0750";
-
-              NoNewPrivileges = true;
-              ProtectSystem = "strict";
-              ProtectHome = true;
-              PrivateTmp = true;
-              PrivateDevices = true;
-              PrivateNetwork = true;
-              ProtectKernelTunables = true;
-              ProtectKernelModules = true;
-              ProtectControlGroups = true;
-              RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-              RestrictNamespaces = true;
-              LockPersonality = true;
-              MemoryDenyWriteExecute = true;
-              RestrictRealtime = true;
-            };
-          }
-      ));
-
-    services.nginx = mkIf usingNginx {
-      enable = true;
-      virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost {
-        forceSSL = mkDefault true;
-        enableACME = mkDefault true;
-        locations."${w.webLocation}" = {
-          extraConfig = ''
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header X-Forwarded-Proto $scheme;
-            proxy_set_header X-Forwarded-Host $host;
-            proxy_set_header X-Forwarded-Server $host;
-
-            proxy_pass http://unix:/run/moin/${name}/gunicorn.sock;
-          '';
-        };
-      });
-    };
-
-    systemd.tmpfiles.rules = [
-      "d  /run/moin            0750 ${user} ${group} - -"
-      "d  ${dataDir}           0550 ${user} ${group} - -"
-    ]
-    ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [
-      "d  ${dataDir}/${wikiIdent}                      0750 ${user} ${group} - -"
-      "d  ${dataDir}/${wikiIdent}/config               0550 ${user} ${group} - -"
-      "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py -    -       -        - ${wikiConfigFile wikiIdent wiki}"
-      # needed in order to pass module name to gunicorn
-      "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py  -    -       -        - ${pkg}/share/moin/server/moin.wsgi"
-      # seed data files
-      "C  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - ${pkg}/share/moin/data"
-      # fix nix store permissions
-      "Z  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - -"
-    ])));
-  };
-
-  meta.maintainers = with lib.maintainers; [ mmilata ];
-}
diff --git a/nixpkgs/nixos/modules/services/web-apps/moodle.nix b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
index 6f5cfa2e3481..19f3e754691e 100644
--- a/nixpkgs/nixos/modules/services/web-apps/moodle.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
@@ -57,7 +57,7 @@ let
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
   phpExt = pkgs.php74.withExtensions
-        ({ enabled, all }: with all; [ iconv mbstring curl openssl tokenizer xmlrpc soap ctype zip gd simplexml dom  intl json sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter ]);
+        ({ enabled, all }: with all; [ iconv mbstring curl openssl tokenizer xmlrpc soap ctype zip gd simplexml dom  intl json sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter opcache ]);
 in
 {
   # interface
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
index 62ae763b69bc..6692d67081c5 100644
--- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
@@ -153,7 +153,7 @@ in {
     package = mkOption {
       type = types.package;
       description = "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud20" "nextcloud21" "nextcloud22" ];
+      relatedPackages = [ "nextcloud21" "nextcloud22" "nextcloud23" ];
     };
     phpPackage = mkOption {
       type = types.package;
@@ -499,6 +499,7 @@ in {
     occ = mkOption {
       type = types.package;
       default = occ;
+      defaultText = literalDocBook "generated script";
       internal = true;
       description = ''
         The nextcloud-occ program preconfigured to target this Nextcloud instance.
@@ -507,14 +508,8 @@ in {
   };
 
   config = mkIf cfg.enable (mkMerge [
-    { assertions = let acfg = cfg.config; in [
-        { assertion = versionOlder cfg.package.version "21" -> cfg.config.defaultPhoneRegion == null;
-          message = "The `defaultPhoneRegion'-setting is only supported for Nextcloud >=21!";
-        }
-      ];
-
-      warnings = let
-        latest = 22;
+    { warnings = let
+        latest = 23;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -532,8 +527,8 @@ in {
         # 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")
+          # All currently supported Nextcloud versions are affected (https://github.com/nextcloud/server/issues/25436).
+          (versionOlder cfg.package.version "24")
           # This module uses mysql
           && (cfg.config.dbtype == "mysql")
           # MySQL is managed via NixOS
@@ -547,9 +542,9 @@ in {
           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 "23") (upgradeWarning 22 "22.05"))
         ++ (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
@@ -574,9 +569,14 @@ 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
+          else if versionOlder stateVersion "22.05" then nextcloud22
+          else nextcloud23
         );
 
       services.nextcloud.datadir = mkOptionDefault config.services.nextcloud.home;
@@ -589,7 +589,7 @@ in {
     { systemd.timers.nextcloud-cron = {
         wantedBy = [ "timers.target" ];
         timerConfig.OnBootSec = "5m";
-        timerConfig.OnUnitActiveSec = "15m";
+        timerConfig.OnUnitActiveSec = "5m";
         timerConfig.Unit = "nextcloud-cron.service";
       };
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
index 9d9cb8dfb3f2..8f55086a2bd1 100644
--- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
@@ -11,7 +11,7 @@
   desktop client is packaged at <literal>pkgs.nextcloud-client</literal>.
  </para>
  <para>
-  The current default by NixOS is <package>nextcloud22</package> which is also the latest
+  The current default by NixOS is <package>nextcloud23</package> which is also the latest
   major version available.
  </para>
  <section xml:id="module-services-nextcloud-basic-usage">
diff --git a/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix b/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix
new file mode 100644
index 000000000000..9e90c01e0bbb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/openwebrx.nix
@@ -0,0 +1,34 @@
+{ 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;
+      defaultText = literalExpression "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/nixpkgs/nixos/modules/services/web-apps/peertube.nix b/nixpkgs/nixos/modules/services/web-apps/peertube.nix
new file mode 100644
index 000000000000..a65428018260
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/peertube.nix
@@ -0,0 +1,465 @@
+{ lib, pkgs, config, options, ... }:
+
+let
+  cfg = config.services.peertube;
+  opt = options.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;
+        defaultText = lib.literalExpression ''
+          if config.${opt.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;
+        defaultText = lib.literalExpression ''
+          if config.${opt.redis.createLocally} && !config.${opt.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;
+        defaultText = lib.literalExpression ''
+          if config.${opt.redis.createLocally} && config.${opt.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;
+        defaultText = lib.literalExpression "config.${opt.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;
+      defaultText = lib.literalExpression "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/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index 5642627d397d..faf0ce13238e 100644
--- a/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -6,6 +6,7 @@ let
 
   cfg = config.services.pgpkeyserver-lite;
   sksCfg = config.services.sks;
+  sksOpt = options.services.sks;
 
   webPkg = cfg.package;
 
@@ -37,6 +38,7 @@ in
 
       hkpAddress = mkOption {
         default = builtins.head sksCfg.hkpAddress;
+        defaultText = literalExpression "head config.${sksOpt.hkpAddress}";
         type = types.str;
         description = "
           Wich ip address the sks-keyserver is listening on.
@@ -45,6 +47,7 @@ in
 
       hkpPort = mkOption {
         default = sksCfg.hkpPort;
+        defaultText = literalExpression "config.${sksOpt.hkpPort}";
         type = types.int;
         description = "
           Which port the sks-keyserver is listening on.
diff --git a/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix b/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix
index 5ac3bc5226b2..f4bf43f56b98 100644
--- a/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/plantuml-server.nix
@@ -58,8 +58,8 @@ in
 
       graphvizPackage = mkOption {
         type = types.package;
-        default = pkgs.graphviz_2_32;
-        defaultText = literalExpression "pkgs.graphviz_2_32";
+        default = pkgs.graphviz;
+        defaultText = literalExpression "pkgs.graphviz";
         description = "Package containing the dot executable.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.nix b/nixpkgs/nixos/modules/services/web-apps/plausible.nix
index b56848b79d21..b6c48186a1d3 100644
--- a/nixpkgs/nixos/modules/services/web-apps/plausible.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix b/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix
new file mode 100644
index 000000000000..ce99b606c318
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/powerdns-admin.nix
@@ -0,0 +1,149 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.powerdns-admin;
+
+  configText = ''
+    ${cfg.config}
+  ''
+  + optionalString (cfg.secretKeyFile != null) ''
+    with open('${cfg.secretKeyFile}') as file:
+      SECRET_KEY = file.read()
+  ''
+  + optionalString (cfg.saltFile != null) ''
+    with open('${cfg.saltFile}') as file:
+      SALT = file.read()
+  '';
+in
+{
+  options.services.powerdns-admin = {
+    enable = mkEnableOption "the PowerDNS web interface";
+
+    extraArgs = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = literalExpression ''
+        [ "-b" "127.0.0.1:8000" ]
+      '';
+      description = ''
+        Extra arguments passed to powerdns-admin.
+      '';
+    };
+
+    config = mkOption {
+      type = types.str;
+      default = "";
+      example = ''
+        BIND_ADDRESS = '127.0.0.1'
+        PORT = 8000
+        SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql'
+      '';
+      description = ''
+        Configuration python file.
+        See <link xlink:href="https://github.com/ngoduykhanh/PowerDNS-Admin/blob/v${pkgs.powerdns-admin.version}/configs/development.py">the example configuration</link>
+        for options.
+      '';
+    };
+
+    secretKeyFile = mkOption {
+      type = types.nullOr types.path;
+      example = "/etc/powerdns-admin/secret";
+      description = ''
+        The secret used to create cookies.
+        This needs to be set, otherwise the default is used and everyone can forge valid login cookies.
+        Set this to null to ignore this setting and configure it through another way.
+      '';
+    };
+
+    saltFile = mkOption {
+      type = types.nullOr types.path;
+      example = "/etc/powerdns-admin/salt";
+      description = ''
+        The salt used for serialization.
+        This should be set, otherwise the default is used.
+        Set this to null to ignore this setting and configure it through another way.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.powerdns-admin = {
+      description = "PowerDNS web interface";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+
+      environment.FLASK_CONF = builtins.toFile "powerdns-admin-config.py" configText;
+      environment.PYTHONPATH = pkgs.powerdns-admin.pythonPath;
+      serviceConfig = {
+        ExecStart = "${pkgs.powerdns-admin}/bin/powerdns-admin --pid /run/powerdns-admin/pid ${escapeShellArgs cfg.extraArgs}";
+        ExecStartPre = "${pkgs.coreutils}/bin/env FLASK_APP=${pkgs.powerdns-admin}/share/powerdnsadmin/__init__.py ${pkgs.python3Packages.flask}/bin/flask db upgrade -d ${pkgs.powerdns-admin}/share/migrations";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStop = "${pkgs.coreutils}/bin/kill -TERM $MAINPID";
+        PIDFile = "/run/powerdns-admin/pid";
+        RuntimeDirectory = "powerdns-admin";
+        User = "powerdnsadmin";
+        Group = "powerdnsadmin";
+
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/hosts"
+          "-/etc/localtime"
+        ]
+        ++ (optional (cfg.secretKeyFile != null) cfg.secretKeyFile)
+        ++ (optional (cfg.saltFile != null) cfg.saltFile);
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+        # ProtectClock= adds DeviceAllow=char-rtc r
+        DeviceAllow = "";
+        # Implies ProtectSystem=strict, which re-mounts all paths
+        #DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        # Needs to start a server
+        #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";
+        # gunicorn needs setuid
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged @resources @keyring"
+          # These got removed by the line above but are needed
+          "@setuid @chown"
+        ];
+        TemporaryFileSystem = "/:ro";
+        # Does not work well with the temporary root
+        #UMask = "0066";
+      };
+    };
+
+    users.groups.powerdnsadmin = { };
+    users.users.powerdnsadmin = {
+      description = "PowerDNS web interface user";
+      isSystemUser = true;
+      group = "powerdnsadmin";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/trac.nix b/nixpkgs/nixos/modules/services/web-apps/trac.nix
deleted file mode 100644
index 207fb857438a..000000000000
--- a/nixpkgs/nixos/modules/services/web-apps/trac.nix
+++ /dev/null
@@ -1,79 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.trac;
-
-  inherit (lib) mkEnableOption mkIf mkOption types;
-
-in {
-
-  options = {
-
-    services.trac = {
-      enable = mkEnableOption "Trac service";
-
-      listen = {
-        ip = mkOption {
-          type = types.str;
-          default = "0.0.0.0";
-          description = ''
-            IP address that Trac should listen on.
-          '';
-        };
-
-        port = mkOption {
-          type = types.port;
-          default = 8000;
-          description = ''
-            Listen port for Trac.
-          '';
-        };
-      };
-
-      dataDir = mkOption {
-        default = "/var/lib/trac";
-        type = types.path;
-        description = ''
-            The directory for storing the Trac data.
-        '';
-      };
-
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Open ports in the firewall for Trac.
-        '';
-      };
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-
-    systemd.services.trac = {
-      description = "Trac server";
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        DynamicUser = true;
-        StateDirectory = baseNameOf cfg.dataDir;
-        ExecStart = ''
-          ${pkgs.trac}/bin/tracd -s \
-            -b ${toString cfg.listen.ip} \
-            -p ${toString cfg.listen.port} \
-            ${cfg.dataDir}
-        '';
-      };
-      preStart = ''
-        if [ ! -e ${cfg.dataDir}/VERSION ]; then
-          ${pkgs.trac}/bin/trac-admin ${cfg.dataDir} initenv Trac "sqlite:db/trac.db"
-        fi
-      '';
-    };
-
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.listen.port ];
-    };
-
-  };
-}
diff --git a/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
index 08356cee1dfe..9aa38ab25c9a 100644
--- a/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
@@ -18,11 +18,11 @@ let
   tt-rss-config = let
     password =
       if (cfg.database.password != null) then
-        "${(escape ["'" "\\"] cfg.database.password)}"
+        "'${(escape ["'" "\\"] cfg.database.password)}'"
       else if (cfg.database.passwordFile != null) then
-        "file_get_contents('${cfg.database.passwordFile}'"
+        "file_get_contents('${cfg.database.passwordFile}')"
       else
-        ""
+        null
       ;
   in pkgs.writeText "config.php" ''
     <?php
@@ -40,7 +40,7 @@ let
       putenv('TTRSS_DB_HOST=${optionalString (cfg.database.host != null) cfg.database.host}');
       putenv('TTRSS_DB_USER=${cfg.database.user}');
       putenv('TTRSS_DB_NAME=${cfg.database.name}');
-      putenv('TTRSS_DB_PASS=${password}');
+      putenv('TTRSS_DB_PASS=' ${optionalString (password != null) ". ${password}"});
       putenv('TTRSS_DB_PORT=${toString dbPort}');
 
       putenv('TTRSS_AUTH_AUTO_CREATE=${boolToString cfg.auth.autoCreate}');
diff --git a/nixpkgs/nixos/modules/services/web-apps/youtrack.nix b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
index 7a70ae6cd523..b83265ffeab6 100644
--- a/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
@@ -128,6 +128,7 @@ in
         Type = "simple";
         User = "youtrack";
         Group = "youtrack";
+        Restart = "on-failure";
         ExecStart = ''${cfg.package}/bin/youtrack --J-Xmx${cfg.maxMemory} --J-XX:MaxMetaspaceSize=${cfg.maxMetaspaceSize} ${cfg.jvmOpts} ${cfg.address}:${toString cfg.port}'';
       };
     };
diff --git a/nixpkgs/nixos/modules/services/web-apps/zabbix.nix b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
index 21567896a89e..538dac0d5be2 100644
--- a/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 let
 
@@ -6,6 +6,7 @@ let
   inherit (lib) literalExpression mapAttrs optionalString versionAtLeast;
 
   cfg = config.services.zabbixWeb;
+  opt = options.services.zabbixWeb;
   fpm = config.services.phpfpm.pools.zabbix;
 
   user = "zabbix";
@@ -21,7 +22,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}';
@@ -81,6 +83,11 @@ in
             if cfg.database.type == "mysql" then config.services.mysql.port
             else if cfg.database.type == "pgsql" then config.services.postgresql.port
             else 1521;
+          defaultText = literalExpression ''
+            if config.${opt.database.type} == "mysql" then config.${options.services.mysql.port}
+            else if config.${opt.database.type} == "pgsql" then config.${options.services.postgresql.port}
+            else 1521
+          '';
           description = "Database host port.";
         };
 
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
index 992a58875e43..1a49b4ca15c7 100644
--- a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -154,7 +154,7 @@ let
       sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
       sslServerChain = if useACME then "${sslCertDir}/chain.pem" else hostOpts.sslServerChain;
 
-      acmeChallenge = optionalString useACME ''
+      acmeChallenge = optionalString (useACME && hostOpts.acmeRoot != null) ''
         Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
         <Directory "${hostOpts.acmeRoot}">
             AllowOverride None
@@ -677,9 +677,16 @@ in
     };
 
     security.acme.certs = let
-      acmePairs = map (hostOpts: nameValuePair hostOpts.hostName {
+      acmePairs = map (hostOpts: let
+        hasRoot = hostOpts.acmeRoot != null;
+      in nameValuePair hostOpts.hostName {
         group = mkDefault cfg.group;
-        webroot = hostOpts.acmeRoot;
+        # if acmeRoot is null inherit config.security.acme
+        # Since config.security.acme.certs.<cert>.webroot's own default value
+        # should take precedence set priority higher than mkOptionDefault
+        webroot = mkOverride (if hasRoot then 1000 else 2000) hostOpts.acmeRoot;
+        # Also nudge dnsProvider to null in case it is inherited
+        dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
         extraDomainNames = hostOpts.serverAliases;
         # Use the vhost-specific email address if provided, otherwise let
         # security.acme.email or security.acme.certs.<cert>.email be used.
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
index 8bb7e91ec9cd..c52ab2c596e0 100644
--- a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
@@ -128,9 +128,12 @@ in
     };
 
     acmeRoot = mkOption {
-      type = types.str;
+      type = types.nullOr types.str;
       default = "/var/lib/acme/acme-challenge";
-      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+      description = ''
+        Directory for the acme challenge which is PUBLIC, don't put certs or keys in here.
+        Set to null to inherit from config.security.acme.
+      '';
     };
 
     sslServerCert = mkOption {
diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix b/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix
index cef27e2e59f3..d51effa31c97 100644
--- a/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy/default.nix
@@ -4,111 +4,159 @@ with lib;
 
 let
   cfg = config.services.caddy;
-  vhostToConfig = vhostName: vhostAttrs: ''
-    ${vhostName} ${builtins.concatStringsSep " " vhostAttrs.serverAliases} {
-      ${vhostAttrs.extraConfig}
-    }
-  '';
-  configFile = pkgs.writeText "Caddyfile" (builtins.concatStringsSep "\n"
-    ([ cfg.config ] ++ (mapAttrsToList vhostToConfig cfg.virtualHosts)));
-
-  formattedConfig = pkgs.runCommand "formattedCaddyFile" { } ''
-    ${cfg.package}/bin/caddy fmt ${configFile} > $out
-  '';
-
-  tlsConfig = {
-    apps.tls.automation.policies = [{
-      issuers = [{
-        inherit (cfg) ca email;
-        module = "acme";
-      }];
-    }];
-  };
 
-  adaptedConfig = pkgs.runCommand "caddy-config-adapted.json" { } ''
-    ${cfg.package}/bin/caddy adapt \
-      --config ${formattedConfig} --adapter ${cfg.adapter} > $out
-  '';
-  tlsJSON = pkgs.writeText "tls.json" (builtins.toJSON tlsConfig);
-
-  # merge the TLS config options we expose with the ones originating in the Caddyfile
-  configJSON =
-    if cfg.ca != null then
-      let tlsConfigMerge = ''
-        {"apps":
-          {"tls":
-            {"automation":
-              {"policies":
-                (if .[0].apps.tls.automation.policies == .[1]?.apps.tls.automation.policies
-                 then .[0].apps.tls.automation.policies
-                 else (.[0].apps.tls.automation.policies + .[1]?.apps.tls.automation.policies)
-                 end)
-              }
-            }
-          }
-        }'';
-      in
-      pkgs.runCommand "caddy-config.json" { } ''
-        ${pkgs.jq}/bin/jq -s '.[0] * ${tlsConfigMerge}' ${adaptedConfig} ${tlsJSON} > $out
+  virtualHosts = attrValues cfg.virtualHosts;
+  acmeVHosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts;
+
+  mkVHostConf = hostOpts:
+    let
+      sslCertDir = config.security.acme.certs.${hostOpts.useACMEHost}.directory;
+    in
       ''
-    else
-      adaptedConfig;
+        ${hostOpts.hostName} ${concatStringsSep " " hostOpts.serverAliases} {
+          bind ${concatStringsSep " " hostOpts.listenAddresses}
+          ${optionalString (hostOpts.useACMEHost != null) "tls ${sslCertDir}/cert.pem ${sslCertDir}/key.pem"}
+          log {
+            ${hostOpts.logFormat}
+          }
+
+          ${hostOpts.extraConfig}
+        }
+      '';
+
+  configFile =
+    let
+      Caddyfile = pkgs.writeText "Caddyfile" ''
+        {
+          ${optionalString (cfg.email != null) "email ${cfg.email}"}
+          ${optionalString (cfg.acmeCA != null) "acme_ca ${cfg.acmeCA}"}
+          log {
+            ${cfg.logFormat}
+          }
+        }
+        ${cfg.extraConfig}
+      '';
+
+      Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
+        ${cfg.package}/bin/caddy fmt ${Caddyfile} > $out
+      '';
+    in
+      if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile;
 in
 {
   imports = [
     (mkRemovedOptionModule [ "services" "caddy" "agree" ] "this option is no longer necessary for Caddy 2")
+    (mkRenamedOptionModule [ "services" "caddy" "ca" ] [ "services" "caddy" "acmeCA" ])
+    (mkRenamedOptionModule [ "services" "caddy" "config" ] [ "services" "caddy" "extraConfig" ])
   ];
 
+  # interface
   options.services.caddy = {
     enable = mkEnableOption "Caddy web server";
 
-    config = mkOption {
-      default = "";
-      example = ''
-        example.com {
-          encode gzip
-          log
-          root /srv/http
-        }
+    user = mkOption {
+      default = "caddy";
+      type = types.str;
+      description = ''
+        User account under which caddy runs.
+
+        <note><para>
+          If left as the default value this user will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the user exists before the Caddy service starts.
+        </para></note>
       '';
-      type = types.lines;
+    };
+
+    group = mkOption {
+      default = "caddy";
+      type = types.str;
       description = ''
-        Verbatim Caddyfile to use.
-        Caddy v2 supports multiple config formats via adapters (see <option>services.caddy.adapter</option>).
+        Group account under which caddy runs.
+
+        <note><para>
+          If left as the default value this user will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the user exists before the Caddy service starts.
+        </para></note>
       '';
     };
 
-    virtualHosts = mkOption {
-      type = types.attrsOf (types.submodule (import ./vhost-options.nix {
-        inherit config lib;
-      }));
-      default = { };
-      example = literalExpression ''
-        {
-          "hydra.example.com" = {
-            serverAliases = [ "www.hydra.example.com" ];
-            extraConfig = ''''''
-              encode gzip
-              log
-              root /srv/http
-            '''''';
-          };
-        };
+    package = mkOption {
+      default = pkgs.caddy;
+      defaultText = literalExpression "pkgs.caddy";
+      type = types.package;
+      description = ''
+        Caddy package to use.
       '';
-      description = "Declarative vhost config";
     };
 
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/caddy";
+      description = ''
+        The data directory for caddy.
 
-    user = mkOption {
-      default = "caddy";
-      type = types.str;
-      description = "User account under which caddy runs.";
+        <note>
+          <para>
+            If left as the default value this directory will automatically be created
+            before the Caddy server starts, otherwise you are responsible for ensuring
+            the directory exists with appropriate ownership and permissions.
+          </para>
+          <para>
+            Caddy v2 replaced <literal>CADDYPATH</literal> with XDG directories.
+            See <link xlink:href="https://caddyserver.com/docs/conventions#file-locations"/>.
+          </para>
+        </note>
+      '';
     };
 
-    group = mkOption {
-      default = "caddy";
-      type = types.str;
-      description = "Group account under which caddy runs.";
+    logDir = mkOption {
+      type = types.path;
+      default = "/var/log/caddy";
+      description = ''
+        Directory for storing Caddy access logs.
+
+        <note><para>
+          If left as the default value this directory will automatically be created
+          before the Caddy server starts, otherwise the sysadmin is responsible for
+          ensuring the directory exists with appropriate ownership and permissions.
+        </para></note>
+      '';
+    };
+
+    logFormat = mkOption {
+      type = types.lines;
+      default = ''
+        level ERROR
+      '';
+      example = literalExpression ''
+        mkForce "level INFO";
+      '';
+      description = ''
+        Configuration for the default logger. See
+        <link xlink:href="https://caddyserver.com/docs/caddyfile/options#log"/>
+        for details.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.path;
+      default = configFile;
+      defaultText = "A Caddyfile automatically generated by values from services.caddy.*";
+      example = literalExpression ''
+        pkgs.writeText "Caddyfile" '''
+          example.com
+
+          root * /var/www/wordpress
+          php_fastcgi unix//run/php/php-version-fpm.sock
+          file_server
+        ''';
+      '';
+      description = ''
+        Override the configuration file used by Caddy. By default,
+        NixOS generates one automatically.
+      '';
     };
 
     adapter = mkOption {
@@ -117,7 +165,13 @@ in
       type = types.str;
       description = ''
         Name of the config adapter to use.
-        See https://caddyserver.com/docs/config-adapters for the full list.
+        See <link xlink:href="https://caddyserver.com/docs/config-adapters"/>
+        for the full list.
+
+        <note><para>
+          Any value other than <literal>caddyfile</literal> is only valid when
+          providing your own <option>configFile</option>.
+        </para></note>
       '';
     };
 
@@ -125,80 +179,110 @@ in
       default = false;
       type = types.bool;
       description = ''
-        Use saved config, if any (and prefer over configuration passed with <option>services.caddy.config</option>).
+        Use saved config, if any (and prefer over any specified configuration passed with <literal>--config</literal>).
       '';
     };
 
-    ca = mkOption {
-      default = "https://acme-v02.api.letsencrypt.org/directory";
-      example = "https://acme-staging-v02.api.letsencrypt.org/directory";
-      type = types.nullOr types.str;
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        example.com {
+          encode gzip
+          log
+          root /srv/http
+        }
+      '';
       description = ''
-        Certificate authority ACME server. The default (Let's Encrypt
-        production server) should be fine for most people. Set it to null if
-        you don't want to include any authority (or if you want to write a more
-        fine-graned configuration manually)
+        Additional lines of configuration appended to the automatically
+        generated <literal>Caddyfile</literal>.
       '';
     };
 
-    email = mkOption {
-      default = "";
-      type = types.str;
-      description = "Email address (for Let's Encrypt certificate)";
+    virtualHosts = mkOption {
+      type = with types; attrsOf (submodule (import ./vhost-options.nix { inherit cfg; }));
+      default = {};
+      example = literalExpression ''
+        {
+          "hydra.example.com" = {
+            serverAliases = [ "www.hydra.example.com" ];
+            extraConfig = '''
+              encode gzip
+              root /srv/http
+            ''';
+          };
+        };
+      '';
+      description = ''
+        Declarative specification of virtual hosts served by Caddy.
+      '';
     };
 
-    dataDir = mkOption {
-      default = "/var/lib/caddy";
-      type = types.path;
+    acmeCA = mkOption {
+      default = "https://acme-v02.api.letsencrypt.org/directory";
+      example = "https://acme-staging-v02.api.letsencrypt.org/directory";
+      type = with types; nullOr str;
       description = ''
-        The data directory, for storing certificates. Before 17.09, this
-        would create a .caddy directory. With 17.09 the contents of the
-        .caddy directory are in the specified data directory instead.
+        The URL to the ACME CA's directory. It is strongly recommended to set
+        this to Let's Encrypt's staging endpoint for testing or development.
 
-        Caddy v2 replaced CADDYPATH with XDG directories.
-        See https://caddyserver.com/docs/conventions#file-locations.
+        Set it to <literal>null</literal> if you want to write a more
+        fine-grained configuration manually.
       '';
     };
 
-    package = mkOption {
-      default = pkgs.caddy;
-      defaultText = literalExpression "pkgs.caddy";
-      type = types.package;
+    email = mkOption {
+      default = null;
+      type = with types; nullOr str;
       description = ''
-        Caddy package to use.
+        Your email address. Mainly used when creating an ACME account with your
+        CA, and is highly recommended in case there are problems with your
+        certificates.
       '';
     };
+
   };
 
+  # implementation
   config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.adapter != "caddyfile" -> cfg.configFile != configFile;
+        message = "Any value other than 'caddyfile' is only valid when providing your own `services.caddy.configFile`";
+      }
+    ];
+
+    services.caddy.extraConfig = concatMapStringsSep "\n" mkVHostConf virtualHosts;
+
+    systemd.packages = [ cfg.package ];
     systemd.services.caddy = {
-      description = "Caddy web server";
-      # upstream unit: https://github.com/caddyserver/dist/blob/master/init/caddy.service
-      after = [ "network-online.target" ];
-      wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
+      wants = map (hostOpts: "acme-finished-${hostOpts.useACMEHost}.target") acmeVHosts;
+      after = map (hostOpts: "acme-selfsigned-${hostOpts.useACMEHost}.service") acmeVHosts;
+      before = map (hostOpts: "acme-${hostOpts.useACMEHost}.service") acmeVHosts;
+
       wantedBy = [ "multi-user.target" ];
       startLimitIntervalSec = 14400;
       startLimitBurst = 10;
+
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/caddy run ${optionalString cfg.resume "--resume"} --config ${configJSON}";
-        ExecReload = "${cfg.package}/bin/caddy reload --config ${configJSON}";
-        Type = "simple";
+        # https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=
+        # If the empty string is assigned to this option, the list of commands to start is reset, prior assignments of this option will have no effect.
+        ExecStart = [ "" "${cfg.package}/bin/caddy run --config ${cfg.configFile} --adapter ${cfg.adapter} ${optionalString cfg.resume "--resume"}" ];
+        ExecReload = [ "" "${cfg.package}/bin/caddy reload --config ${cfg.configFile} --adapter ${cfg.adapter}" ];
+
+        ExecStartPre = "${cfg.package}/bin/caddy validate --config ${cfg.configFile} --adapter ${cfg.adapter}";
         User = cfg.user;
         Group = cfg.group;
+        ReadWriteDirectories = cfg.dataDir;
+        StateDirectory = mkIf (cfg.dataDir == "/var/lib/caddy") [ "caddy" ];
+        LogsDirectory = mkIf (cfg.logDir == "/var/log/caddy") [ "caddy" ];
         Restart = "on-abnormal";
-        AmbientCapabilities = "cap_net_bind_service";
-        CapabilityBoundingSet = "cap_net_bind_service";
+        SupplementaryGroups = mkIf (length acmeVHosts != 0) [ "acme" ];
+
+        # TODO: attempt to upstream these options
         NoNewPrivileges = true;
-        LimitNPROC = 512;
-        LimitNOFILE = 1048576;
-        PrivateTmp = true;
         PrivateDevices = true;
         ProtectHome = true;
-        ProtectSystem = "full";
-        ReadWriteDirectories = cfg.dataDir;
-        KillMode = "mixed";
-        KillSignal = "SIGQUIT";
-        TimeoutStopSec = "5s";
       };
     };
 
@@ -207,7 +291,6 @@ in
         group = cfg.group;
         uid = config.ids.uids.caddy;
         home = cfg.dataDir;
-        createHome = true;
       };
     };
 
@@ -215,5 +298,12 @@ in
       caddy.gid = config.ids.gids.caddy;
     };
 
+    security.acme.certs =
+      let
+        eachACMEHost = unique (catAttrs "useACMEHost" acmeVHosts);
+        reloads = map (useACMEHost: nameValuePair useACMEHost { reloadServices = [ "caddy.service" ]; }) eachACMEHost;
+      in
+        listToAttrs reloads;
+
   };
 }
diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix
index 1f74295fc9a2..f240ec605c29 100644
--- a/nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy/vhost-options.nix
@@ -1,15 +1,19 @@
-# This file defines the options that can be used both for the Nginx
-# main server configuration, and for the virtual hosts.  (The latter
-# has additional options that affect the web server as a whole, like
-# the user/group to run under.)
-
-{ lib, ... }:
-
-with lib;
+{ cfg }:
+{ config, lib, name, ... }:
+let
+  inherit (lib) literalExpression mkOption types;
+in
 {
   options = {
+
+    hostName = mkOption {
+      type = types.str;
+      default = name;
+      description = "Canonical hostname for the server.";
+    };
+
     serverAliases = mkOption {
-      type = types.listOf types.str;
+      type = with types; listOf str;
       default = [ ];
       example = [ "www.example.org" "example.org" ];
       description = ''
@@ -17,12 +21,59 @@ with lib;
       '';
     };
 
+    listenAddresses = mkOption {
+      type = with types; listOf str;
+      description = ''
+        A list of host interfaces to bind to for this virtual host.
+      '';
+      default = [ ];
+      example = [ "127.0.0.1" "::1" ];
+    };
+
+    useACMEHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        A host of an existing Let's Encrypt certificate to use.
+        This is mostly useful if you use DNS challenges but Caddy does not
+        currently support your provider.
+
+        <emphasis>Note that this option does not create any certificates, nor
+        does it add subdomains to existing ones – you will need to create them
+        manually using <xref linkend="opt-security.acme.certs"/>. Additionally,
+        you should probably add the <literal>caddy</literal> user to the
+        <literal>acme</literal> group to grant access to the certificates.</emphasis>
+      '';
+    };
+
+    logFormat = mkOption {
+      type = types.lines;
+      default = ''
+        output file ${cfg.logDir}/access-${config.hostName}.log
+      '';
+      defaultText = ''
+        output file ''${config.services.caddy.logDir}/access-''${hostName}.log
+      '';
+      example = literalExpression ''
+        mkForce '''
+          output discard
+        ''';
+      '';
+      description = ''
+        Configuration for HTTP request logging (also known as access logs). See
+        <link xlink:href="https://caddyserver.com/docs/caddyfile/directives/log#log"/>
+        for details.
+      '';
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
       description = ''
-        These lines go into the vhost verbatim
+        Additional lines of configuration appended to this virtual host in the
+        automatically generated <literal>Caddyfile</literal>.
       '';
     };
+
   };
 }
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
index 3f262451c2cb..5f091591daf9 100644
--- a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.lighttpd.collectd;
+  opt = options.services.lighttpd.collectd;
 
   collectionConf = pkgs.writeText "collection.conf" ''
     datadir: "${config.services.collectd.dataDir}"
@@ -29,6 +30,9 @@ in
     collectionCgi = mkOption {
       type = types.path;
       default = defaultCollectionCgi;
+      defaultText = literalDocBook ''
+        <literal>config.${options.services.collectd.package}</literal> configured for lighttpd
+      '';
       description = ''
         Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
         This option allows to use a customized version
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
index d5486be65ee7..dc174c8b41d0 100644
--- a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -79,6 +79,11 @@ let
       # we use a list of mime types from the mailcap package, which is also
       # used by most other Linux distributions by default.
       include ${pkgs.mailcap}/etc/nginx/mime.types;
+      # When recommendedOptimisation is disabled nginx fails to start because the mailmap mime.types database
+      # contains 1026 enries and the default is only 1024. Setting to a higher number to remove the need to
+      # overwrite it because nginx does not allow duplicated settings.
+      types_hash_max_size 4096;
+
       include ${cfg.package}/conf/fastcgi.conf;
       include ${cfg.package}/conf/uwsgi_params;
 
@@ -113,7 +118,6 @@ let
         tcp_nopush on;
         tcp_nodelay on;
         keepalive_timeout 65;
-        types_hash_max_size 4096;
       ''}
 
       ssl_protocols ${cfg.sslProtocols};
@@ -274,7 +278,7 @@ let
         acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) ''
           location /.well-known/acme-challenge {
             ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
-            root ${vhost.acmeRoot};
+            ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"}
             auth_basic off;
           }
           ${optionalString (vhost.acmeFallbackHost != null) ''
@@ -316,6 +320,9 @@ let
           ${optionalString vhost.rejectSSL ''
             ssl_reject_handshake on;
           ''}
+          ${optionalString (hasSSL && vhost.kTLS) ''
+            ssl_conf_command Options KTLS;
+          ''}
 
           ${mkBasicAuth vhostName vhost}
 
@@ -821,6 +828,14 @@ in
       }
 
       {
+        assertion = any (host: host.kTLS) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.21.4";
+        message = ''
+          services.nginx.virtualHosts.<name>.kTLS requires nginx version
+          1.21.4 or above; see the documentation for services.nginx.package.
+        '';
+      }
+
+      {
         assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts);
         message = ''
           Options services.nginx.service.virtualHosts.<name>.enableACME and
@@ -889,14 +904,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 @mount @obsolete @privileged @setuid @mincore" ] ++ optionals (cfg.package != pkgs.tengine) [ "~@ipc" ];
       };
     };
 
@@ -933,9 +948,16 @@ in
     };
 
     security.acme.certs = let
-      acmePairs = map (vhostConfig: nameValuePair vhostConfig.serverName {
+      acmePairs = map (vhostConfig: let
+        hasRoot = vhostConfig.acmeRoot != null;
+      in nameValuePair vhostConfig.serverName {
         group = mkDefault cfg.group;
-        webroot = vhostConfig.acmeRoot;
+        # if acmeRoot is null inherit config.security.acme
+        # Since config.security.acme.certs.<cert>.webroot's own default value
+        # should take precedence set priority higher than mkOptionDefault
+        webroot = mkOverride (if hasRoot then 1000 else 2000) vhostConfig.acmeRoot;
+        # Also nudge dnsProvider to null in case it is inherited
+        dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
         extraDomainNames = vhostConfig.serverAliases;
       # Filter for enableACME-only vhosts. Don't want to create dud certs
       }) (filter (vhostConfig: vhostConfig.useACMEHost == null) acmeEnabledVhosts);
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
index 56a5381e05c8..6fd00b386974 100644
--- a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -102,7 +102,7 @@ with lib;
     };
 
     fastcgiParams = mkOption {
-      type = types.attrsOf types.str;
+      type = types.attrsOf (types.either types.str types.path);
       default = {};
       description = ''
         FastCGI parameters to override.  Unlike in the Nginx
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 7ee041d37211..c4e8285dc48b 100644
--- a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -3,7 +3,7 @@
 # has additional options that affect the web server as a whole, like
 # the user/group to run under.)
 
-{ lib, ... }:
+{ config, lib, ... }:
 
 with lib;
 {
@@ -85,9 +85,12 @@ with lib;
     };
 
     acmeRoot = mkOption {
-      type = types.str;
+      type = types.nullOr types.str;
       default = "/var/lib/acme/acme-challenge";
-      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+      description = ''
+        Directory for the acme challenge which is PUBLIC, don't put certs or keys in here.
+        Set to null to inherit from config.security.acme.
+      '';
     };
 
     acmeFallbackHost = mkOption {
@@ -147,6 +150,17 @@ with lib;
       '';
     };
 
+    kTLS = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable kTLS support.
+        Implementing TLS in the kernel (kTLS) improves performance by significantly
+        reducing the need for copying operations between user space and the kernel.
+        Required Nginx version 1.21.4 or later.
+      '';
+    };
+
     sslCertificate = mkOption {
       type = types.path;
       example = "/var/host.cert";
diff --git a/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix b/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix
index 706ea5bfefba..b52087fa038c 100644
--- a/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/trafficserver/default.nix
@@ -61,7 +61,7 @@ in
 
     ipAllow = mkOption {
       type = types.nullOr yaml.type;
-      default = builtins.fromJSON (builtins.readFile ./ip_allow.json);
+      default = lib.importJSON ./ip_allow.json;
       defaultText = literalDocBook "upstream defaults";
       example = literalExpression ''
         {
@@ -84,7 +84,7 @@ in
 
     logging = mkOption {
       type = types.nullOr yaml.type;
-      default = builtins.fromJSON (builtins.readFile ./logging.json);
+      default = lib.importJSON ./logging.json;
       defaultText = literalDocBook "upstream defaults";
       example = { };
       description = ''
diff --git a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
index 3411b89cf6d5..576648d134bd 100644
--- a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
@@ -121,7 +121,7 @@ in {
               moin = {
                 type = "normal";
                 pythonPackages = self: with self; [ moinmoin ];
-                socket = "${config.services.uwsgi.runDir}/uwsgi.sock";
+                socket = "''${config.services.uwsgi.runDir}/uwsgi.sock";
               };
             };
           }
diff --git a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
index 0ebf58eb9f61..fe817313a993 100644
--- a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
@@ -42,6 +42,7 @@ in
       stateDir = mkOption {
         type = types.path;
         default = "/var/spool/varnish/${config.networking.hostName}";
+        defaultText = literalExpression ''"/var/spool/varnish/''${config.networking.hostName}"'';
         description = "
           Directory holding all state for Varnish to run.
         ";
diff --git a/nixpkgs/nixos/modules/services/x11/clight.nix b/nixpkgs/nixos/modules/services/x11/clight.nix
index 873f425fb8be..d994a658cbaa 100644
--- a/nixpkgs/nixos/modules/services/x11/clight.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index a0a5873f72fe..82b07206a8b6 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -202,6 +202,13 @@ in
         blueberry
         warpinator
 
+        # cinnamon xapps
+        xviewer
+        xreader
+        xed
+        xplayer
+        pix
+
         # external apps shipped with linux-mint
         hexchat
         gnome-calculator
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix
index 1e316c379f5b..efc9bd39b366 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -453,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
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml
index 6c53bacacb32..e5da7740196e 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome.xml
@@ -126,21 +126,6 @@
 </programlisting>
 
  </section>
- <section xml:id="sec-gnome-gdm">
-  <title>GDM</title>
-
-  <para>
-   If you want to use GNOME Wayland session on Nvidia hardware, you need to enable:
-  </para>
-
-<programlisting>
-<xref linkend="opt-services.xserver.displayManager.gdm.nvidiaWayland"/> = true;
-</programlisting>
-
-  <para>
-   as the default configuration will forbid this.
-  </para>
- </section>
 
  <section xml:id="sec-gnome-icons-and-gtk-themes">
   <title>Icons and GTK Themes</title>
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 112f493b811c..980a6b939d5a 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -221,13 +221,16 @@ in
       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.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini";
 
-      xdg.portal.extraPortals = [
-        pkgs.pantheon.elementary-files
+      xdg.portal.extraPortals = with pkgs.pantheon; [
+        elementary-files
+        elementary-settings-daemon
+        xdg-desktop-portal-pantheon
       ];
 
       # Override GSettings schemas
@@ -265,7 +268,7 @@ in
 
       fonts.fontconfig.defaultFonts = {
         monospace = [ "Roboto Mono" ];
-        sansSerif = [ "Open Sans" ];
+        sansSerif = [ "Inter" ];
       };
     })
 
@@ -280,6 +283,7 @@ in
         elementary-music
         elementary-photos
         elementary-screenshot
+        elementary-tasks
         elementary-terminal
         elementary-videos
         epiphany
@@ -292,9 +296,10 @@ in
     })
 
     (mkIf serviceCfg.contractor.enable {
-      environment.systemPackages = with  pkgs.pantheon; [
+      environment.systemPackages = with pkgs.pantheon; [
         contractor
-        extra-elementary-contracts
+        file-roller-contract
+        gnome-bluetooth-contract
       ];
 
       environment.pathsToLink = [
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 11cb4d3b8a95..9bacdaa9be98 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -1,19 +1,40 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   xcfg = config.services.xserver;
   cfg = xcfg.desktopManager.plasma5;
 
+  # Use only for **internal** options.
+  # This is not exactly user-friendly.
+  kdeConfigurationType = with types;
+    let
+      valueTypes = (oneOf [
+        bool
+        float
+        int
+        str
+      ]) // {
+        description = "KDE Configuration value";
+        emptyValue.value = "";
+      };
+      set = (nullOr (lazyAttrsOf valueTypes)) // {
+        description = "KDE Configuration set";
+        emptyValue.value = {};
+      };
+    in (lazyAttrsOf set) // {
+        description = "KDE Configuration file";
+        emptyValue.value = {};
+      };
+
   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";
-  sed = "${getBin pkgs.gnused}/bin/sed";
+  ini = pkgs.formats.ini { };
 
   gtkrc2 = writeText "gtkrc-2.0" ''
     # Default GTK+ 2 config for NixOS Plasma 5
@@ -33,23 +54,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}
@@ -75,7 +98,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
@@ -87,95 +110,118 @@ 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
 
-    '';
+      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.";
+    };
 
-      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;
+    };
+
+    # Internally allows configuring kdeglobals globally
+    kdeglobals = mkOption {
+      internal = true;
+      default = {};
+      type = kdeConfigurationType;
+    };
+
+    # Internally allows configuring kwin globally
+    kwinrc = mkOption {
+      internal = true;
+      default = {};
+      type = kdeConfigurationType;
+    };
+
+    mobile.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable support for running the Plasma Mobile shell.
+      '';
     };
 
+    mobile.installRecommendedSoftware = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Installs software recommended for use with Plasma Mobile, but which
+        is not strictly required for Plasma Mobile to run.
+      '';
+    };
   };
 
   imports = [
@@ -184,36 +230,28 @@ 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.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-workspace ];
+    # Common Plasma dependencies
+    (mkIf (cfg.enable || cfg.mobile.enable) {
 
       security.wrappers = {
-        kcheckpass =
-          { setuid = true;
-            owner = "root";
-            group = "root";
-            source = "${lib.getBin libsForQt5.kscreenlocker}/libexec/kcheckpass";
-          };
-        start_kdeinit =
-          { setuid = true;
-            owner = "root";
-            group = "root";
-            source = "${lib.getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
-          };
-        kwin_wayland =
-          { owner = "root";
-            group = "root";
-            capabilities = "cap_sys_nice+ep";
-            source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
-          };
+        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 = {
+          owner = "root";
+          group = "root";
+          capabilities = "cap_sys_nice+ep";
+          source = "${getBin plasma5.kwin}/bin/kwin_wayland";
+        };
       };
 
       # DDC support
@@ -247,7 +285,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
@@ -279,50 +317,34 @@ in
           kdeplasma-addons
           kgamma5
           khotkeys
-          kinfocenter
-          kmenuedit
           kscreen
           kscreenlocker
-          ksystemstats
           kwayland
           kwin
           kwrited
           libkscreen
           libksysguard
           milou
-          plasma-systemmonitor
           plasma-browser-integration
           plasma-integration
           polkit-kde-agent
-          spectacle
-          systemsettings
 
           plasma-desktop
           plasma-workspace
           plasma-workspace-wallpapers
 
-          dolphin
-          dolphin-plugins
-          ffmpegthumbs
-          kdegraphics-thumbnailers
-          khelpcenter
-          kio-extras
           konsole
           oxygen
-          print-manager
 
           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
@@ -336,6 +358,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;
 
@@ -361,9 +384,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
@@ -385,6 +411,15 @@ 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;
+        };
+      };
+
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ];
 
@@ -393,7 +428,151 @@ in
       services.xserver.displayManager.setupCommands = startplasma;
 
       nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
+
+      environment.etc = {
+        "xdg/kwinrc".text     = lib.generators.toINI {} cfg.kwinrc;
+        "xdg/kdeglobals".text = lib.generators.toINI {} cfg.kdeglobals;
+      };
     })
-  ];
 
+    # Plasma Desktop
+    (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.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";
+
+      environment.systemPackages =
+        with libsForQt5;
+        with plasma5; with kdeGear; with kdeFrameworks;
+        [
+          ksystemstats
+          kinfocenter
+          kmenuedit
+          plasma-systemmonitor
+          spectacle
+          systemsettings
+
+          dolphin
+          dolphin-plugins
+          ffmpegthumbs
+          kdegraphics-thumbnailers
+          khelpcenter
+          kio-extras
+          print-manager
+
+          elisa
+          gwenview
+          okular
+        ]
+      ;
+
+      systemd.user.services = {
+        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}
+          '';
+        };
+      };
+    })
+
+    # Plasma Mobile
+    (mkIf cfg.mobile.enable {
+      assertions = [
+        {
+          # The user interface breaks without NetworkManager
+          assertion = config.networking.networkmanager.enable;
+          message = "Plasma Mobile requires NetworkManager.";
+        }
+        {
+          # The user interface breaks without bluetooth
+          assertion = config.hardware.bluetooth.enable;
+          message = "Plasma Mobile requires Bluetooth.";
+        }
+        {
+          # The user interface breaks without pulse
+          assertion = config.hardware.pulseaudio.enable;
+          message = "Plasma Mobile requires pulseaudio.";
+        }
+      ];
+
+      environment.systemPackages =
+        with libsForQt5;
+        with plasma5; with kdeApplications; with kdeFrameworks;
+        [
+          # Basic packages without which Plasma Mobile fails to work properly.
+          plasma-phone-components
+          plasma-nano
+          pkgs.maliit-framework
+          pkgs.maliit-keyboard
+        ]
+        ++ lib.optionals (cfg.mobile.installRecommendedSoftware) (with libsForQt5.plasmaMobileGear;[
+          # Additional software made for Plasma Mobile.
+          alligator
+          angelfish
+          audiotube
+          calindori
+          kalk
+          kasts
+          kclock
+          keysmith
+          koko
+          krecorder
+          ktrip
+          kweather
+          plasma-dialer
+          plasma-phonebook
+          plasma-settings
+          spacebar
+        ])
+      ;
+
+      # The following services are needed or the UI is broken.
+      hardware.bluetooth.enable = true;
+      hardware.pulseaudio.enable = true;
+      networking.networkmanager.enable = true;
+
+      # Recommendations can be found here:
+      #  - https://invent.kde.org/plasma-mobile/plasma-phone-settings/-/tree/master/etc/xdg
+      # This configuration is the minimum required for Plasma Mobile to *work*.
+      services.xserver.desktopManager.plasma5 = {
+        kdeglobals = {
+          KDE = {
+            # This forces a numeric PIN for the lockscreen, which is the
+            # recommendation from upstream.
+            LookAndFeelPackage = lib.mkDefault "org.kde.plasma.phone";
+          };
+        };
+        kwinrc = {
+          Windows = {
+            # Forces windows to be maximized
+            Placement = lib.mkDefault "Maximizing";
+          };
+          "org.kde.kdecoration2" = {
+            # No decorations (title bar)
+            NoPlugin = lib.mkDefault "true";
+          };
+        };
+      };
+
+      services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-phone-components ];
+    })
+  ];
 }
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
index 25276e1d649e..3cf92f98c56f 100644
--- a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -9,7 +9,7 @@ in
 {
 
   meta = {
-    maintainers = with maintainers; [ ];
+    maintainers = teams.xfce.members;
   };
 
   imports = [
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
index 7fc8db95a485..92b3af8527f1 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
@@ -7,13 +7,14 @@
 # (e.g., KDE, Gnome or a plain xterm), and optionally the *window
 # manager* (e.g. kwin or twm).
 
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
 
   cfg = config.services.xserver;
+  opt = options.services.xserver;
   xorg = pkgs.xorg;
 
   fontconfig = config.fonts.fontconfig;
@@ -122,10 +123,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}
     '';
@@ -147,6 +148,7 @@ in
       xauthBin = mkOption {
         internal = true;
         default = "${xorg.xauth}/bin/xauth";
+        defaultText = literalExpression ''"''${pkgs.xorg.xauth}/bin/xauth"'';
         description = "Path to the <command>xauth</command> program used by display managers.";
       };
 
@@ -278,9 +280,12 @@ in
             defaultSessionFromLegacyOptions
           else
             null;
+        defaultText = literalDocBook ''
+          Taken from display manager settings or window manager settings, if either is set.
+        '';
         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.
         '';
@@ -337,11 +342,12 @@ in
 
       # Configuration for automatic login. Common for all DM.
       autoLogin = mkOption {
-        type = types.submodule {
+        type = types.submodule ({ config, options, ... }: {
           options = {
             enable = mkOption {
               type = types.bool;
-              default = cfg.displayManager.autoLogin.user != null;
+              default = config.user != null;
+              defaultText = literalExpression "config.${options.user} != null";
               description = ''
                 Automatically log in as <option>autoLogin.user</option>.
               '';
@@ -355,7 +361,7 @@ in
               '';
             };
           };
-        };
+        });
 
         default = {};
         description = ''
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
index e036c684c886..6f0d645725e9 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
@@ -83,14 +83,14 @@ in
         default = true;
         description = ''
           Allow GDM to run on Wayland instead of Xserver.
-          Note to enable Wayland with Nvidia you need to
-          enable the <option>nvidiaWayland</option>.
+          Note to enable Wayland with Nvidia the <option>nvidiaWayland</option>
+          must not be disabled.
         '';
       };
 
       nvidiaWayland = mkOption {
         type = types.bool;
-        default = false;
+        default = true;
         description = ''
           Whether to allow wayland to be used with the proprietary
           NVidia graphics driver.
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
index 1c9a5f978c54..84b75c83aeab 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -146,7 +146,7 @@ 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 = literalExpression "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
         description = ''
@@ -312,7 +312,7 @@ in
     };
 
     systemd.tmpfiles.rules = [
-      "d /run/lightdm 0711 lightdm lightdm 0"
+      "d /run/lightdm 0711 lightdm lightdm -"
       "d /var/cache/lightdm 0711 root lightdm -"
       "d /var/lib/lightdm 1770 lightdm lightdm -"
       "d /var/lib/lightdm-data 1775 lightdm lightdm -"
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
index 5a4fad9c4cb1..529a086381f0 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixpkgs/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 = {
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix b/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix
index 6cd46cdf9649..a48566ae0684 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix b/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix
index 73d27390a580..e30977364300 100644
--- a/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/sx.nix
@@ -26,13 +26,8 @@ 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/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
index e2fb7d0918e3..efdb7c61dfae 100644
--- a/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixpkgs/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>,
@@ -166,8 +170,9 @@ 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
+          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).
         '';
       };
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix b/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
index 22af869f1f8a..93dd560bca40 100644
--- a/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
+++ b/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
@@ -1,8 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let cfg = config.services.xserver.synaptics;
+    opt = options.services.xserver.synaptics;
     tapConfig = if cfg.tapButtons then enabledTapConfig else disabledTapConfig;
     enabledTapConfig = ''
       Option "MaxTapTime" "180"
@@ -77,24 +78,28 @@ in {
       horizTwoFingerScroll = mkOption {
         type = types.bool;
         default = cfg.twoFingerScroll;
+        defaultText = literalExpression "config.${opt.twoFingerScroll}";
         description = "Whether to enable horizontal two-finger drag-scrolling.";
       };
 
       vertTwoFingerScroll = mkOption {
         type = types.bool;
         default = cfg.twoFingerScroll;
+        defaultText = literalExpression "config.${opt.twoFingerScroll}";
         description = "Whether to enable vertical two-finger drag-scrolling.";
       };
 
       horizEdgeScroll = mkOption {
         type = types.bool;
         default = ! cfg.horizTwoFingerScroll;
+        defaultText = literalExpression "! config.${opt.horizTwoFingerScroll}";
         description = "Whether to enable horizontal edge drag-scrolling.";
       };
 
       vertEdgeScroll = mkOption {
         type = types.bool;
         default = ! cfg.vertTwoFingerScroll;
+        defaultText = literalExpression "! config.${opt.vertTwoFingerScroll}";
         description = "Whether to enable vertical edge drag-scrolling.";
       };
 
diff --git a/nixpkgs/nixos/modules/services/x11/picom.nix b/nixpkgs/nixos/modules/services/x11/picom.nix
index dbd4b1cefef1..b40e20bcd357 100644
--- a/nixpkgs/nixos/modules/services/x11/picom.nix
+++ b/nixpkgs/nixos/modules/services/x11/picom.nix
@@ -1,10 +1,11 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
 let
 
   cfg = config.services.picom;
+  opt = options.services.picom;
 
   pairOf = x: with types;
     addCheck (listOf x) (y: length y == 2)
@@ -178,7 +179,16 @@ in {
 
     wintypes = mkOption {
       type = types.attrs;
-      default = { popup_menu = { opacity = cfg.menuOpacity; }; dropdown_menu = { opacity = cfg.menuOpacity; }; };
+      default = {
+        popup_menu = { opacity = cfg.menuOpacity; };
+        dropdown_menu = { opacity = cfg.menuOpacity; };
+      };
+      defaultText = literalExpression ''
+        {
+          popup_menu = { opacity = config.${opt.menuOpacity}; };
+          dropdown_menu = { opacity = config.${opt.menuOpacity}; };
+        }
+      '';
       example = {};
       description = ''
         Rules for specific window types.
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
index 6aa0d5f76f26..ecad411ff683 100644
--- a/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -29,7 +29,6 @@ let
       } ''
         install -D ${xmonadEnv}/share/man/man1/xmonad.1.gz $out/share/man/man1/xmonad.1.gz
         makeWrapper ${configured}/bin/xmonad $out/bin/xmonad \
-          --set NIX_GHC "${xmonadEnv}/bin/ghc" \
           --set XMONAD_XMESSAGE "${pkgs.xorg.xmessage}/bin/xmessage"
       '';
 
@@ -40,10 +39,12 @@ in {
   options = {
     services.xserver.windowManager.xmonad = {
       enable = mkEnableOption "xmonad";
+
       haskellPackages = mkOption {
         default = pkgs.haskellPackages;
         defaultText = literalExpression "pkgs.haskellPackages";
         example = literalExpression "pkgs.haskell.packages.ghc784";
+        type = types.attrs;
         description = ''
           haskellPackages used to build Xmonad and other packages.
           This can be used to change the GHC version used to build
@@ -93,6 +94,8 @@ in {
           <literal>(restart "xmonad" True)</literal> instead, which will just restart
           xmonad from PATH. This allows e.g. switching to the new xmonad binary
           after rebuilding your system with nixos-rebuild.
+          For the same reason, ghc is not added to the environment when this
+          option is set.
 
           If you actually want to run xmonad with a config specified here, but
           also be able to recompile and restart it from a copy of that source in
diff --git a/nixpkgs/nixos/modules/services/x11/xserver.nix b/nixpkgs/nixos/modules/services/x11/xserver.nix
index cb620f10b13f..24d925734423 100644
--- a/nixpkgs/nixos/modules/services/x11/xserver.nix
+++ b/nixpkgs/nixos/modules/services/x11/xserver.nix
@@ -588,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;
 
@@ -702,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.
diff --git a/nixpkgs/nixos/modules/system/activation/activation-script.nix b/nixpkgs/nixos/modules/system/activation/activation-script.nix
index 8dbfe393f109..d6f14d01dbaa 100644
--- a/nixpkgs/nixos/modules/system/activation/activation-script.nix
+++ b/nixpkgs/nixos/modules/system/activation/activation-script.nix
@@ -142,6 +142,7 @@ in
       readOnly = true;
       internal = true;
       default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
+      defaultText = literalDocBook "generated activation script";
     };
 
     system.userActivationScripts = mkOption {
@@ -150,7 +151,7 @@ in
       example = literalExpression ''
         { plasmaSetup = {
             text = '''
-              ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
+              ''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
             ''';
             deps = [];
           };
diff --git a/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl b/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
index e105502cf3a4..3fbab8b94c93 100644
--- a/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl
@@ -18,14 +18,11 @@ 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.
+# Parse restart/reload requests by the activation script
 my $restartByActivationFile = "/run/nixos/activation-restart-list";
+my $reloadByActivationFile = "/run/nixos/activation-reload-list";
 my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list";
+my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list";
 
 make_path("/run/nixos", { mode => oct(755) });
 
@@ -152,8 +149,9 @@ sub fingerprintUnit {
 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$/) {
+    if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/ || $unit =~ /\.slice$/) {
         # 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.
@@ -161,6 +159,11 @@ sub handleModifiedUnit {
         # Reload the changed mount unit to force a remount.
         $unitsToReload->{$unit} = 1;
         recordUnit($reloadListFile, $unit);
+    } elsif ($unit =~ /\.socket$/) {
+        # FIXME: do something?
+        # Attempt to fix this: https://github.com/NixOS/nixpkgs/pull/141192
+        # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
+        # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
     } else {
         my $unitInfo = parseUnit($newUnitFile);
         if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) {
@@ -170,66 +173,47 @@ sub handleModifiedUnit {
         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;
+            # It doesn't make sense to stop and start non-services because
+            # they can't have ExecStop=
+            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 {
+                # 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;
+                            }
                         }
                     }
                 }
-            }
 
-            # 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.
+                # 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;
                 }
+
+                $unitsToStop->{$unit} = 1;
             }
         }
     }
@@ -307,7 +291,7 @@ while (my ($unit, $state) = each %{$activePrev}) {
         }
 
         elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) {
-            handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToSkip);
+            handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip);
         }
     }
 }
@@ -378,8 +362,14 @@ foreach my $device (keys %$prevSwaps) {
 
 # Should we have systemd re-exec itself?
 my $prevSystemd = abs_path("/proc/1/exe") // "/unknown";
+my $prevSystemdSystemConfig = abs_path("/etc/systemd/system.conf") // "/unknown";
 my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die;
+my $newSystemdSystemConfig = abs_path("$out/etc/systemd/system.conf") // "/unknown";
+
 my $restartSystemd = $prevSystemd ne $newSystemd;
+if ($prevSystemdSystemConfig ne $newSystemdSystemConfig) {
+    $restartSystemd = 1;
+}
 
 
 sub filterUnits {
@@ -392,6 +382,8 @@ sub filterUnits {
 }
 
 my @unitsToStopFiltered = filterUnits(\%unitsToStop);
+my @unitsToStartFiltered = filterUnits(\%unitsToStart);
+
 
 # Show dry-run actions.
 if ($action eq "dry-activate") {
@@ -403,44 +395,21 @@ 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;
-    }
+    $unitsToRestart{$_} = 1 foreach
+        split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "");
 
-    print STDERR "would NOT restart the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n"
-        if scalar(keys %unitsToAlsoSkip) > 0;
+    $unitsToReload{$_} = 1 foreach
+        split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // "");
 
     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;
+    unlink($dryRestartByActivationFile);
+    unlink($dryReloadByActivationFile);
     exit 0;
 }
 
@@ -465,38 +434,12 @@ 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));
-}
+# stopped are already stopped before the activation script is run.
+$unitsToRestart{$_} = 1 foreach
+    split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "");
 
-print STDERR "NOT restarting the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n"
-    if scalar(keys %unitsToAlsoSkip) > 0;
+$unitsToReload{$_} = 1 foreach
+    split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // "");
 
 # Restart systemd if necessary. Note that this is done using the
 # current version of systemd, just in case the new one has trouble
@@ -537,40 +480,14 @@ if (scalar(keys %unitsToReload) > 0) {
     print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n";
     system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4;
     unlink($reloadListFile);
+    unlink($reloadByActivationFile);
 }
 
 # Restart changed services (those that have to be restarted rather
 # than stopped and started).
 if (scalar(keys %unitsToRestart) > 0) {
     print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n";
-
-    # 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";
-        }
-    }
+    system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4;
     unlink($restartListFile);
     unlink($restartByActivationFile);
 }
@@ -581,7 +498,6 @@ 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;
diff --git a/nixpkgs/nixos/modules/system/activation/top-level.nix b/nixpkgs/nixos/modules/system/activation/top-level.nix
index 68da910d29cc..501998fa399e 100644
--- a/nixpkgs/nixos/modules/system/activation/top-level.nix
+++ b/nixpkgs/nixos/modules/system/activation/top-level.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, modules, baseModules, specialArgs, ... }:
+{ config, lib, pkgs, extendModules, noUserModules, ... }:
 
 with lib;
 
@@ -11,16 +11,10 @@ let
   # you can provide an easy way to boot the same configuration
   # as you use, but with another kernel
   # !!! fix this
-  children = mapAttrs (childName: childConfig:
-      (import ../../../lib/eval-config.nix {
-        inherit lib baseModules specialArgs;
-        system = config.nixpkgs.initialSystem;
-        modules =
-           (optionals childConfig.inheritParentConfig modules)
-        ++ [ ./no-clone.nix ]
-        ++ [ childConfig.configuration ];
-      }).config.system.build.toplevel
-    ) config.specialisation;
+  children =
+    mapAttrs
+      (childName: childConfig: childConfig.configuration.system.build.toplevel)
+      config.specialisation;
 
   systemBuilder =
     let
@@ -176,7 +170,11 @@ in
         </screen>
       '';
       type = types.attrsOf (types.submodule (
-        { ... }: {
+        local@{ ... }: let
+          extend = if local.config.inheritParentConfig
+            then extendModules
+            else noUserModules.extendModules;
+        in {
           options.inheritParentConfig = mkOption {
             type = types.bool;
             default = true;
@@ -185,7 +183,15 @@ in
 
           options.configuration = mkOption {
             default = {};
-            description = "Arbitrary NixOS configuration options.";
+            description = ''
+              Arbitrary NixOS configuration.
+
+              Anything you can add to a normal NixOS configuration, you can add
+              here, including imports and config values, although nested
+              specialisations will be ignored.
+            '';
+            visible = "shallow";
+            inherit (extend { modules = [ ./no-clone.nix ]; }) type;
           };
         })
       );
@@ -202,6 +208,7 @@ in
     system.boot.loader.kernelFile = mkOption {
       internal = true;
       default = pkgs.stdenv.hostPlatform.linux-kernel.target;
+      defaultText = literalExpression "pkgs.stdenv.hostPlatform.linux-kernel.target";
       type = types.str;
       description = ''
         Name of the kernel file to be passed to the bootloader.
diff --git a/nixpkgs/nixos/modules/system/boot/binfmt.nix b/nixpkgs/nixos/modules/system/boot/binfmt.nix
index 2408ecc80d22..fdb4d0e4c7fb 100644
--- a/nixpkgs/nixos/modules/system/boot/binfmt.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/system/boot/kernel.nix b/nixpkgs/nixos/modules/system/boot/kernel.nix
index 4a9da9394519..d147155d796c 100644
--- a/nixpkgs/nixos/modules/system/boot/kernel.nix
+++ b/nixpkgs/nixos/modules/system/boot/kernel.nix
@@ -243,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/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
index fa8500dd42bd..8db271f87135 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/grub.nix
@@ -99,6 +99,7 @@ in
 
       enable = mkOption {
         default = !config.boot.isContainer;
+        defaultText = literalExpression "!config.boot.isContainer";
         type = types.bool;
         description = ''
           Whether to enable the GNU GRUB boot loader.
diff --git a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
index 4d8537d4c327..0c93b288fc65 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixpkgs/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -103,10 +103,10 @@ if (stat($bootPath)->dev != stat("/nix/store")->dev) {
 
 # Discover information about the location of the bootPath
 struct(Fs => {
-        device => '$',
-        type => '$',
-        mount => '$',
-    });
+    device => '$',
+    type => '$',
+    mount => '$',
+});
 sub PathInMount {
     my ($path, $mount) = @_;
     my @splitMount = split /\//, $mount;
@@ -155,9 +155,9 @@ sub GetFs {
     return $bestFs;
 }
 struct (Grub => {
-        path => '$',
-        search => '$',
-    });
+    path => '$',
+    search => '$',
+});
 my $driveid = 1;
 sub GrubFs {
     my ($dir) = @_;
@@ -254,8 +254,8 @@ if ($grubVersion == 1) {
     # $defaultEntry might be "saved", indicating that we want to use the last selected configuration as default.
     # Incidentally this is already the correct value for the grub 1 config to achieve this behaviour.
     $conf .= "
-    default $defaultEntry
-    timeout $timeout
+        default $defaultEntry
+        timeout $timeout
     ";
     if ($splashImage) {
         copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
@@ -305,7 +305,7 @@ else {
 
     if ($copyKernels == 0) {
         $conf .= "
-        " . $grubStore->search;
+            " . $grubStore->search;
     }
     # FIXME: should use grub-mkconfig.
     my $defaultEntryText = $defaultEntry;
@@ -313,55 +313,55 @@ else {
         $defaultEntryText = "\"\${saved_entry}\"";
     }
     $conf .= "
-    " . $grubBoot->search . "
-    if [ -s \$prefix/grubenv ]; then
-    load_env
-    fi
-
-    # ‘grub-reboot’ sets a one-time saved entry, which we process here and
-    # then delete.
-    if [ \"\${next_entry}\" ]; then
-    set default=\"\${next_entry}\"
-    set next_entry=
-    save_env next_entry
-    set timeout=1
-    set boot_once=true
-    else
-    set default=$defaultEntryText
-    set timeout=$timeout
-    fi
-
-    function savedefault {
-        if [ -z \"\${boot_once}\"]; then
-        saved_entry=\"\${chosen}\"
-        save_env saved_entry
+        " . $grubBoot->search . "
+        if [ -s \$prefix/grubenv ]; then
+          load_env
         fi
-    }
 
-    # Setup the graphics stack for bios and efi systems
-    if [ \"\${grub_platform}\" = \"efi\" ]; then
-    insmod efi_gop
-    insmod efi_uga
-    else
-    insmod vbe
-    fi
+        # ‘grub-reboot’ sets a one-time saved entry, which we process here and
+        # then delete.
+        if [ \"\${next_entry}\" ]; then
+          set default=\"\${next_entry}\"
+          set next_entry=
+          save_env next_entry
+          set timeout=1
+          set boot_once=true
+        else
+          set default=$defaultEntryText
+          set timeout=$timeout
+        fi
+
+        function savedefault {
+            if [ -z \"\${boot_once}\"]; then
+            saved_entry=\"\${chosen}\"
+            save_env saved_entry
+            fi
+        }
+
+        # Setup the graphics stack for bios and efi systems
+        if [ \"\${grub_platform}\" = \"efi\" ]; then
+          insmod efi_gop
+          insmod efi_uga
+        else
+          insmod vbe
+        fi
     ";
 
     if ($font) {
         copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
         $conf .= "
-        insmod font
-        if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
-        insmod gfxterm
-        if [ \"\${grub_platform}\" = \"efi\" ]; then
-        set gfxmode=$gfxmodeEfi
-        set gfxpayload=$gfxpayloadEfi
-        else
-        set gfxmode=$gfxmodeBios
-        set gfxpayload=$gfxpayloadBios
-        fi
-        terminal_output gfxterm
-        fi
+            insmod font
+            if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
+              insmod gfxterm
+              if [ \"\${grub_platform}\" = \"efi\" ]; then
+                set gfxmode=$gfxmodeEfi
+                set gfxpayload=$gfxpayloadEfi
+              else
+                set gfxmode=$gfxmodeBios
+                set gfxpayload=$gfxpayloadBios
+              fi
+              terminal_output gfxterm
+            fi
         ";
     }
     if ($splashImage) {
@@ -378,14 +378,14 @@ else {
         }
         copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
         $conf .= "
-        insmod " . substr($suffix, 1) . "
-        if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
-        set color_normal=white/black
-        set color_highlight=black/white
-        else
-        set menu_color_normal=cyan/blue
-        set menu_color_highlight=white/blue
-        fi
+            insmod " . substr($suffix, 1) . "
+            if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
+              set color_normal=white/black
+              set color_highlight=black/white
+            else
+              set menu_color_normal=cyan/blue
+              set menu_color_highlight=white/blue
+            fi
         ";
     }
 
@@ -395,20 +395,20 @@ else {
         # Copy theme
         rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
         $conf .= "
-        # Sets theme.
-        set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
-        export theme
-        # Load theme fonts, if any
+            # Sets theme.
+            set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
+            export theme
+            # Load theme fonts, if any
         ";
 
         find( { wanted => sub {
-                    if ($_ =~ /\.pf2$/i) {
-                        $font = File::Spec->abs2rel($File::Find::name, $theme);
-                        $conf .= "
-                        loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
-                        ";
-                    }
-                }, no_chdir => 1 }, $theme );
+            if ($_ =~ /\.pf2$/i) {
+                $font = File::Spec->abs2rel($File::Find::name, $theme);
+                $conf .= "
+                    loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
+                ";
+            }
+        }, no_chdir => 1 }, $theme );
     }
 }
 
@@ -474,8 +474,8 @@ sub addEntry {
     # FIXME: $confName
 
     my $kernelParams =
-    "init=" . Cwd::abs_path("$path/init") . " " .
-    readFile("$path/kernel-params");
+        "init=" . Cwd::abs_path("$path/init") . " " .
+        readFile("$path/kernel-params");
     my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
 
     if ($grubVersion == 1) {
@@ -524,9 +524,9 @@ foreach my $link (@links) {
 
     my $date = strftime("%F", localtime(lstat($link)->mtime));
     my $version =
-    -e "$link/nixos-version"
-    ? readFile("$link/nixos-version")
-    : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+        -e "$link/nixos-version"
+        ? readFile("$link/nixos-version")
+        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
 
     if ($cfgName) {
         $entryName = $cfgName;
@@ -551,8 +551,8 @@ sub addProfile {
     sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
 
     my @links = sort
-    { nrFromGen($b) <=> nrFromGen($a) }
-    (glob "$profile-*-link");
+        { nrFromGen($b) <=> nrFromGen($a) }
+        (glob "$profile-*-link");
 
     my $curEntry = 0;
     foreach my $link (@links) {
@@ -563,9 +563,9 @@ sub addProfile {
         }
         my $date = strftime("%F", localtime(lstat($link)->mtime));
         my $version =
-        -e "$link/nixos-version"
-        ? readFile("$link/nixos-version")
-        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+            -e "$link/nixos-version"
+            ? readFile("$link/nixos-version")
+            : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
         addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link);
     }
 
@@ -653,13 +653,13 @@ foreach my $fn (glob "$bootPath/kernels/*") {
 #
 
 struct(GrubState => {
-        name => '$',
-        version => '$',
-        efi => '$',
-        devices => '$',
-        efiMountPoint => '$',
-        extraGrubInstallArgs => '@',
-    });
+    name => '$',
+    version => '$',
+    efi => '$',
+    devices => '$',
+    efiMountPoint => '$',
+    extraGrubInstallArgs => '@',
+});
 # If you add something to the state file, only add it to the end
 # because it is read line-by-line.
 sub readGrubState {
diff --git a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 6c26b4e0f87a..e9697b5f0e64 100644
--- a/nixpkgs/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixpkgs/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 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[Tuple[Optional[str], int]]) -> None:
+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 = []
@@ -219,19 +244,27 @@ def main() -> None:
         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]
+        systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
         sdboot_status = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True)
 
         # See status_binaries() in systemd bootctl.c for code which generates this
-        m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot (\d+)\)$",
+        m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
                       sdboot_status, re.IGNORECASE | re.MULTILINE)
+
+        needs_install = False
+
         if m is None:
-            print("could not find any previously installed systemd-boot")
+            print("could not find any previously installed systemd-boot, installing.")
+            # Let systemd-boot attempt an installation if a previous one wasn't found
+            needs_install = True
         else:
-            sdboot_version = m.group(2)
-            if systemd_version > sdboot_version:
+            sdboot_version = f'({m.group(2)})'
+            if systemd_version != sdboot_version:
                 print("updating systemd-boot from %s to %s" % (sdboot_version, systemd_version))
-                subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
+                needs_install = True
+
+        if needs_install:
+            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
 
     mkdir_p("@efiSysMountPoint@/efi/nixos")
     mkdir_p("@efiSysMountPoint@/loader/entries")
@@ -243,6 +276,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/nixpkgs/nixos/modules/system/boot/luksroot.nix b/nixpkgs/nixos/modules/system/boot/luksroot.nix
index fb5269e43d08..f0d3170dc5ac 100644
--- a/nixpkgs/nixos/modules/system/boot/luksroot.nix
+++ b/nixpkgs/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
diff --git a/nixpkgs/nixos/modules/system/boot/networkd.nix b/nixpkgs/nixos/modules/system/boot/networkd.nix
index 662dfe2db989..1145831ee2ea 100644
--- a/nixpkgs/nixos/modules/system/boot/networkd.nix
+++ b/nixpkgs/nixos/modules/system/boot/networkd.nix
@@ -1,8 +1,8 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
+with utils.systemdUtils.unitOptions;
+with utils.systemdUtils.lib;
 with lib;
-with import ./systemd-unit-options.nix { inherit config lib; };
-with import ./systemd-lib.nix { inherit config lib pkgs; };
 
 let
 
@@ -131,6 +131,7 @@ let
           "fou"
           "xfrm"
           "ifb"
+          "batadv"
         ])
         (assertByteFormat "MTUBytes")
         (assertMacAddress "MACAddress")
@@ -381,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 = {
@@ -473,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"])
@@ -547,6 +572,7 @@ let
           "Family"
           "User"
           "SuppressPrefixLength"
+          "Type"
         ])
         (assertInt "TypeOfService")
         (assertRange "TypeOfService" 0 255)
@@ -559,6 +585,7 @@ let
         (assertValueOneOf "Family" ["ipv4" "ipv6" "both"])
         (assertInt "SuppressPrefixLength")
         (assertRange "SuppressPrefixLength" 0 128)
+        (assertValueOneOf "Type" ["blackhole" "unreachable" "prohibit"])
       ];
 
       sectionRoute = checkUnitConfig "Route" [
@@ -796,6 +823,16 @@ let
         (assertValueOneOf "OnLink" boolValues)
       ];
 
+      sectionDHCPServerStaticLease = checkUnitConfig "DHCPServerStaticLease" [
+        (assertOnlyFields [
+          "MACAddress"
+          "Address"
+        ])
+        (assertHasField "MACAddress")
+        (assertHasField "Address")
+        (assertMacAddress "MACAddress")
+      ];
+
     };
   };
 
@@ -1056,6 +1093,21 @@ 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 = {
@@ -1121,6 +1173,25 @@ let
     };
   };
 
+  dhcpServerStaticLeaseOptions = {
+    options = {
+      dhcpServerStaticLeaseConfig = mkOption {
+        default = {};
+        example = { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; };
+        type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServerStaticLease;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[DHCPServerStaticLease]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+
+          Make sure to configure the corresponding client interface to use
+          <literal>ClientIdentifier=mac</literal>.
+        '';
+      };
+    };
+  };
+
   networkOptions = commonNetworkOptions // {
 
     linkConfig = mkOption {
@@ -1233,6 +1304,17 @@ let
       '';
     };
 
+    dhcpServerStaticLeases = mkOption {
+      default = [];
+      example = [ { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; } ];
+      type = with types; listOf (submodule dhcpServerStaticLeaseOptions);
+      description = ''
+        A list of DHCPServerStaticLease sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
     ipv6Prefixes = mkOption {
       default = [];
       example = [ { AddressAutoconfiguration = true; OnLink = true; } ];
@@ -1507,6 +1589,10 @@ let
           [VRF]
           ${attrsToSection def.vrfConfig}
         ''
+        + optionalString (def.batmanAdvancedConfig != { }) ''
+          [BatmanAdvanced]
+          ${attrsToSection def.batmanAdvancedConfig}
+        ''
         + def.extraConfig;
     };
 
@@ -1600,6 +1686,10 @@ let
           [IPv6Prefix]
           ${attrsToSection x.ipv6PrefixConfig}
         '')
+        + flip concatMapStrings def.dhcpServerStaticLeases (x: ''
+          [DHCPServerStaticLease]
+          ${attrsToSection x.dhcpServerStaticLeaseConfig}
+        '')
         + def.extraConfig;
     };
 
diff --git a/nixpkgs/nixos/modules/system/boot/plymouth.nix b/nixpkgs/nixos/modules/system/boot/plymouth.nix
index 4b8194d2f85c..78ae8e9d20b7 100644
--- a/nixpkgs/nixos/modules/system/boot/plymouth.nix
+++ b/nixpkgs/nixos/modules/system/boot/plymouth.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:
 
 with lib;
 
@@ -7,6 +7,7 @@ let
   inherit (pkgs) plymouth nixos-icons;
 
   cfg = config.boot.plymouth;
+  opt = options.boot.plymouth;
 
   nixosBreezePlymouth = pkgs.plasma5Packages.breeze-plymouth.override {
     logoFile = cfg.logo;
@@ -71,6 +72,11 @@ in
 
       themePackages = mkOption {
         default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth;
+        defaultText = literalDocBook ''
+          A NixOS branded variant of the breeze theme when
+          <literal>config.${opt.theme} == "breeze"</literal>, otherwise
+          <literal>[ ]</literal>.
+        '';
         type = types.listOf types.package;
         description = ''
           Extra theme packages for plymouth.
diff --git a/nixpkgs/nixos/modules/system/boot/resolved.nix b/nixpkgs/nixos/modules/system/boot/resolved.nix
index a6fc07da0abb..21d3fab2f35d 100644
--- a/nixpkgs/nixos/modules/system/boot/resolved.nix
+++ b/nixpkgs/nixos/modules/system/boot/resolved.nix
@@ -32,6 +32,7 @@ in
 
     services.resolved.domains = mkOption {
       default = config.networking.search;
+      defaultText = literalExpression "config.networking.search";
       example = [ "example.com" ];
       type = types.listOf types.str;
       description = ''
diff --git a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
index 3dfcc010b64e..98409ed6ae52 100644
--- a/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixpkgs/nixos/modules/system/boot/stage-1-init.sh
@@ -119,6 +119,18 @@ specialMount() {
 }
 source @earlyMountScript@
 
+# Copy initrd secrets from /.initrd-secrets to their actual destinations
+if [ -d "/.initrd-secrets" ]; then
+    #
+    # Secrets are named by their full destination pathname and stored
+    # under /.initrd-secrets/
+    #
+    for secret in $(cd "/.initrd-secrets"; find . -type f); do
+        mkdir -p $(dirname "/$secret")
+        cp "/.initrd-secrets/$secret" "$secret"
+    done
+fi
+
 # Log the script output to /dev/kmsg or /run/log/stage-1-init.log.
 mkdir -p /tmp
 mkfifo /tmp/stage-1-init.log.fifo
@@ -251,15 +263,6 @@ if test -n "$debug1devices"; then fail; fi
 @postDeviceCommands@
 
 
-# Return true if the machine is on AC power, or if we can't determine
-# whether it's on AC power.
-onACPower() {
-    ! test -d "/proc/acpi/battery" ||
-    ! ls /proc/acpi/battery/BAT[0-9]* > /dev/null 2>&1 ||
-    ! cat /proc/acpi/battery/BAT*/state | grep "^charging state" | grep -q "discharg"
-}
-
-
 # Check the specified file system, if appropriate.
 checkFS() {
     local device="$1"
@@ -304,13 +307,6 @@ checkFS() {
         return 0
     fi
 
-    # Don't run `fsck' if the machine is on battery power.  !!! Is
-    # this a good idea?
-    if ! onACPower; then
-        echo "on battery power, so no \`fsck' will be performed on \`$device'"
-        return 0
-    fi
-
     echo "checking $device..."
 
     fsckFlags=
diff --git a/nixpkgs/nixos/modules/system/boot/stage-1.nix b/nixpkgs/nixos/modules/system/boot/stage-1.nix
index adbed9d8d58e..409424a5b0f6 100644
--- a/nixpkgs/nixos/modules/system/boot/stage-1.nix
+++ b/nixpkgs/nixos/modules/system/boot/stage-1.nix
@@ -411,8 +411,8 @@ let
         ${lib.concatStringsSep "\n" (mapAttrsToList (dest: source:
             let source' = if source == null then dest else toString source; in
               ''
-                mkdir -p $(dirname "$tmp/${dest}")
-                cp -a ${source'} "$tmp/${dest}"
+                mkdir -p $(dirname "$tmp/.initrd-secrets/${dest}")
+                cp -a ${source'} "$tmp/.initrd-secrets/${dest}"
               ''
           ) config.boot.initrd.secrets)
          }
diff --git a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh b/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
index 50ee0b8841e5..a90f58042d2d 100644..100755
--- a/nixpkgs/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixpkgs/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
@@ -170,4 +172,5 @@ echo "starting systemd..."
 
 PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \
     LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive @systemdUnitPathEnvVar@ \
+    TZDIR=/etc/zoneinfo \
     exec @systemdExecutable@
diff --git a/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix b/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix
index b450d77429b2..02d2660add89 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd-nspawn.nix
@@ -1,8 +1,8 @@
-{ config, lib , pkgs, ...}:
+{ config, lib, pkgs, utils, ...}:
 
+with utils.systemdUtils.unitOptions;
+with utils.systemdUtils.lib;
 with lib;
-with import ./systemd-unit-options.nix { inherit config lib; };
-with import ./systemd-lib.nix { inherit config lib pkgs; };
 
 let
   cfg = config.systemd.nspawn;
diff --git a/nixpkgs/nixos/modules/system/boot/systemd.nix b/nixpkgs/nixos/modules/system/boot/systemd.nix
index a2b9da2f72e5..f5769b295abc 100644
--- a/nixpkgs/nixos/modules/system/boot/systemd.nix
+++ b/nixpkgs/nixos/modules/system/boot/systemd.nix
@@ -1,9 +1,9 @@
 { config, lib, pkgs, utils, ... }:
 
 with utils;
+with systemdUtils.unitOptions;
+with systemdUtils.lib;
 with lib;
-with import ./systemd-unit-options.nix { inherit config lib; };
-with import ./systemd-lib.nix { inherit config lib pkgs; };
 
 let
 
@@ -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"
 
@@ -65,6 +67,7 @@ let
       "systemd-user-sessions.service"
       "dbus-org.freedesktop.import1.service"
       "dbus-org.freedesktop.machine1.service"
+      "dbus-org.freedesktop.login1.service"
       "user@.service"
       "user-runtime-dir@.service"
 
diff --git a/nixpkgs/nixos/modules/system/boot/timesyncd.nix b/nixpkgs/nixos/modules/system/boot/timesyncd.nix
index 692315dbe99c..5f35a1547696 100644
--- a/nixpkgs/nixos/modules/system/boot/timesyncd.nix
+++ b/nixpkgs/nixos/modules/system/boot/timesyncd.nix
@@ -9,6 +9,7 @@ with lib;
     services.timesyncd = {
       enable = mkOption {
         default = !config.boot.isContainer;
+        defaultText = literalExpression "!config.boot.isContainer";
         type = types.bool;
         description = ''
           Enables the systemd NTP client daemon.
@@ -16,6 +17,7 @@ with lib;
       };
       servers = mkOption {
         default = config.networking.timeServers;
+        defaultText = literalExpression "config.networking.timeServers";
         type = types.listOf types.str;
         description = ''
           The set of NTP servers from which to synchronise.
diff --git a/nixpkgs/nixos/modules/system/etc/etc.nix b/nixpkgs/nixos/modules/system/etc/etc.nix
index 8f14f04a1f64..6cc8c341e6df 100644
--- a/nixpkgs/nixos/modules/system/etc/etc.nix
+++ b/nixpkgs/nixos/modules/system/etc/etc.nix
@@ -85,7 +85,7 @@ in
       '';
 
       type = with types; attrsOf (submodule (
-        { name, config, ... }:
+        { name, config, options, ... }:
         { options = {
 
             enable = mkOption {
@@ -172,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/nixpkgs/nixos/modules/system/etc/setup-etc.pl b/nixpkgs/nixos/modules/system/etc/setup-etc.pl
index eed20065087f..be6b2d9ae71e 100644
--- a/nixpkgs/nixos/modules/system/etc/setup-etc.pl
+++ b/nixpkgs/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/nixpkgs/nixos/modules/tasks/auto-upgrade.nix b/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
index b19b688a1fb8..b931b27ad817 100644
--- a/nixpkgs/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix b/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
index 2c03ef7ba7e0..3bc0dedec00e 100644
--- a/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixpkgs/nixos/modules/tasks/filesystems/zfs.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, utils, ... }:
+{ config, lib, options, pkgs, utils, ... }:
 #
 # TODO: zfs tunables
 
@@ -8,6 +8,7 @@ with lib;
 let
 
   cfgZfs = config.boot.zfs;
+  optZfs = options.boot.zfs;
   cfgExpandOnBoot = config.services.zfs.expandOnBoot;
   cfgSnapshots = config.services.zfs.autoSnapshot;
   cfgSnapFlags = cfgSnapshots.flags;
@@ -112,6 +113,7 @@ in
         readOnly = true;
         type = types.bool;
         default = inInitrd || inSystem;
+        defaultText = literalDocBook "<literal>true</literal> if ZFS filesystem support is enabled";
         description = "True if ZFS filesystem support is enabled";
       };
 
@@ -346,6 +348,7 @@ in
     services.zfs.zed = {
       enableMail = mkEnableOption "ZED's ability to send emails" // {
         default = cfgZfs.package.enableMail;
+        defaultText = literalExpression "config.${optZfs.package}.enableMail";
       };
 
       settings = mkOption {
@@ -561,7 +564,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
@@ -575,7 +579,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/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix b/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
index e8e2de090b32..19f2be2c4a25 100644
--- a/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -532,6 +532,33 @@ let
             '';
           });
 
+        createGreDevice = n: v: nameValuePair "${n}-netdev"
+          (let
+            deps = deviceDependency v.dev;
+          in
+          { description = "GRE Tunnel Interface ${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 = ''
+              # Remove Dead Interfaces
+              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link add name "${n}" type ${v.type} \
+                ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
+                ${optionalString (v.local != null) "local \"${v.local}\""} \
+                ${optionalString (v.dev != null) "dev \"${v.dev}\""}
+              ip link set "${n}" up
+            '';
+            postStop = ''
+              ip link delete "${n}" || true
+            '';
+          });
+
         createVlanDevice = n: v: nameValuePair "${n}-netdev"
           (let
             deps = deviceDependency v.interface;
@@ -570,6 +597,7 @@ let
          // mapAttrs' createMacvlanDevice cfg.macvlans
          // mapAttrs' createFouEncapsulation cfg.fooOverUDP
          // mapAttrs' createSitDevice cfg.sits
+         // mapAttrs' createGreDevice cfg.greTunnels
          // mapAttrs' createVlanDevice cfg.vlans
          // {
            network-setup = networkSetup;
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix b/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
index ccfd7fd4132b..58239ca5452a 100644
--- a/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -18,6 +18,7 @@ let
     concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
     ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
     ++ map (sit: sit.dev) (attrValues cfg.sits)
+    ++ map (gre: gre.dev) (attrValues cfg.greTunnels)
     ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
     # add dependency to physical or independently created vswitch member interface
     # TODO: warn the user that any address configured on those interfaces will be useless
@@ -245,6 +246,25 @@ in
           } ]);
         };
       })))
+      (mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = gre.type;
+          };
+          tunnelConfig =
+            (optionalAttrs (gre.remote != null) {
+              Remote = gre.remote;
+            }) // (optionalAttrs (gre.local != null) {
+              Local = gre.local;
+            });
+        };
+        networks = mkIf (gre.dev != null) {
+          "40-${gre.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+            tunnel = [ name ];
+          } ]);
+        };
+      })))
       (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
         netdevs."40-${name}" = {
           netdevConfig = {
diff --git a/nixpkgs/nixos/modules/tasks/network-interfaces.nix b/nixpkgs/nixos/modules/tasks/network-interfaces.nix
index 4e20ec118464..854badb23f69 100644
--- a/nixpkgs/nixos/modules/tasks/network-interfaces.nix
+++ b/nixpkgs/nixos/modules/tasks/network-interfaces.nix
@@ -6,9 +6,11 @@ with utils;
 let
 
   cfg = config.networking;
+  opt = options.networking;
   interfaces = attrValues cfg.interfaces;
   hasVirtuals = any (i: i.virtual) interfaces;
   hasSits = cfg.sits != { };
+  hasGres = cfg.greTunnels != { };
   hasBonds = cfg.bonds != { };
   hasFous = cfg.fooOverUDP != { }
     || filterAttrs (_: s: s.encapsulation != null) cfg.sits != { };
@@ -417,7 +419,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 {
@@ -992,6 +998,65 @@ in
       });
     };
 
+    networking.greTunnels = mkOption {
+      default = { };
+      example = literalExpression ''
+        {
+          greBridge = {
+            remote = "10.0.0.1";
+            local = "10.0.0.22";
+            dev = "enp4s0f0";
+            type = "tap";
+          };
+        }
+      '';
+      description = ''
+        This option allows you to define Generic Routing Encapsulation (GRE) tunnels.
+      '';
+      type = with types; attrsOf (submodule {
+        options = {
+
+          remote = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "10.0.0.1";
+            description = ''
+              The address of the remote endpoint to forward traffic over.
+            '';
+          };
+
+          local = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "10.0.0.22";
+            description = ''
+              The address of the local endpoint which the remote
+              side should send packets to.
+            '';
+          };
+
+          dev = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "enp4s0f0";
+            description = ''
+              The underlying network device on which the tunnel resides.
+            '';
+          };
+
+          type = mkOption {
+            type = with types; enum [ "tun" "tap" ];
+            default = "tap";
+            example = "tap";
+            apply = v: if v == "tun" then "gre" else "gretap";
+            description = ''
+              Whether the tunnel routes layer 2 (tap) or layer 3 (tun) traffic.
+            '';
+          };
+        };
+      });
+    };
+
     networking.vlans = mkOption {
       default = { };
       example = literalExpression ''
@@ -1165,6 +1230,9 @@ in
 
     networking.tempAddresses = mkOption {
       default = if cfg.enableIPv6 then "default" else "disabled";
+      defaultText = literalExpression ''
+        if ''${config.${opt.enableIPv6}} then "default" else "disabled"
+      '';
       type = types.enum (lib.attrNames tempaddrValues);
       description = ''
         Whether to enable IPv6 Privacy Extensions for interfaces not
@@ -1221,6 +1289,7 @@ in
     boot.kernelModules = [ ]
       ++ optional hasVirtuals "tun"
       ++ optional hasSits "sit"
+      ++ optional hasGres "gre"
       ++ optional hasBonds "bonding"
       ++ optional hasFous "fou";
 
@@ -1233,6 +1302,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
diff --git a/nixpkgs/nixos/modules/tasks/snapraid.nix b/nixpkgs/nixos/modules/tasks/snapraid.nix
index 4529009930fc..c8dde5b48993 100644
--- a/nixpkgs/nixos/modules/tasks/snapraid.nix
+++ b/nixpkgs/nixos/modules/tasks/snapraid.nix
@@ -193,7 +193,6 @@ in
             LockPersonality = true;
             MemoryDenyWriteExecute = true;
             NoNewPrivileges = true;
-            PrivateDevices = true;
             PrivateTmp = true;
             ProtectClock = true;
             ProtectControlGroups = true;
@@ -208,7 +207,8 @@ in
             SystemCallArchitectures = "native";
             SystemCallFilter = "@system-service";
             SystemCallErrorNumber = "EPERM";
-            CapabilityBoundingSet = "CAP_DAC_OVERRIDE";
+            CapabilityBoundingSet = "CAP_DAC_OVERRIDE" +
+              lib.optionalString cfg.touchBeforeSync " CAP_FOWNER";
 
             ProtectSystem = "strict";
             ProtectHome = "read-only";
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix b/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix
new file mode 100644
index 000000000000..91b5237e3371
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-ec2-amis.nix
@@ -0,0 +1,444 @@
+let self = {
+  "14.04".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-71c6f470";
+  "14.04".ap-northeast-1.x86_64-linux.pv-ebs = "ami-4dcbf84c";
+  "14.04".ap-northeast-1.x86_64-linux.pv-s3 = "ami-8fc4f68e";
+  "14.04".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-da280888";
+  "14.04".ap-southeast-1.x86_64-linux.pv-ebs = "ami-7a9dbc28";
+  "14.04".ap-southeast-1.x86_64-linux.pv-s3 = "ami-c4290996";
+  "14.04".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-ab523e91";
+  "14.04".ap-southeast-2.x86_64-linux.pv-ebs = "ami-6769055d";
+  "14.04".ap-southeast-2.x86_64-linux.pv-s3 = "ami-15533f2f";
+  "14.04".eu-central-1.x86_64-linux.hvm-ebs = "ami-ba0234a7";
+  "14.04".eu-west-1.x86_64-linux.hvm-ebs = "ami-96cb63e1";
+  "14.04".eu-west-1.x86_64-linux.pv-ebs = "ami-b48c25c3";
+  "14.04".eu-west-1.x86_64-linux.pv-s3 = "ami-06cd6571";
+  "14.04".sa-east-1.x86_64-linux.hvm-ebs = "ami-01b90e1c";
+  "14.04".sa-east-1.x86_64-linux.pv-ebs = "ami-69e35474";
+  "14.04".sa-east-1.x86_64-linux.pv-s3 = "ami-61b90e7c";
+  "14.04".us-east-1.x86_64-linux.hvm-ebs = "ami-58ba3a30";
+  "14.04".us-east-1.x86_64-linux.pv-ebs = "ami-9e0583f6";
+  "14.04".us-east-1.x86_64-linux.pv-s3 = "ami-9cbe3ef4";
+  "14.04".us-west-1.x86_64-linux.hvm-ebs = "ami-0bc3d74e";
+  "14.04".us-west-1.x86_64-linux.pv-ebs = "ami-8b1703ce";
+  "14.04".us-west-1.x86_64-linux.pv-s3 = "ami-27ccd862";
+  "14.04".us-west-2.x86_64-linux.hvm-ebs = "ami-3bf1bf0b";
+  "14.04".us-west-2.x86_64-linux.pv-ebs = "ami-259bd515";
+  "14.04".us-west-2.x86_64-linux.pv-s3 = "ami-07094037";
+
+  "14.12".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-24435f25";
+  "14.12".ap-northeast-1.x86_64-linux.pv-ebs = "ami-b0425eb1";
+  "14.12".ap-northeast-1.x86_64-linux.pv-s3 = "ami-fed3c6ff";
+  "14.12".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-6c765d3e";
+  "14.12".ap-southeast-1.x86_64-linux.pv-ebs = "ami-6a765d38";
+  "14.12".ap-southeast-1.x86_64-linux.pv-s3 = "ami-d1bf9183";
+  "14.12".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-af86f395";
+  "14.12".ap-southeast-2.x86_64-linux.pv-ebs = "ami-b386f389";
+  "14.12".ap-southeast-2.x86_64-linux.pv-s3 = "ami-69c5ae53";
+  "14.12".eu-central-1.x86_64-linux.hvm-ebs = "ami-4a497a57";
+  "14.12".eu-central-1.x86_64-linux.pv-ebs = "ami-4c497a51";
+  "14.12".eu-central-1.x86_64-linux.pv-s3 = "ami-60f2c27d";
+  "14.12".eu-west-1.x86_64-linux.hvm-ebs = "ami-d126a5a6";
+  "14.12".eu-west-1.x86_64-linux.pv-ebs = "ami-0126a576";
+  "14.12".eu-west-1.x86_64-linux.pv-s3 = "ami-deda5fa9";
+  "14.12".sa-east-1.x86_64-linux.hvm-ebs = "ami-2d239e30";
+  "14.12".sa-east-1.x86_64-linux.pv-ebs = "ami-35239e28";
+  "14.12".sa-east-1.x86_64-linux.pv-s3 = "ami-81e3519c";
+  "14.12".us-east-1.x86_64-linux.hvm-ebs = "ami-0c463a64";
+  "14.12".us-east-1.x86_64-linux.pv-ebs = "ami-ac473bc4";
+  "14.12".us-east-1.x86_64-linux.pv-s3 = "ami-00e18a68";
+  "14.12".us-west-1.x86_64-linux.hvm-ebs = "ami-ca534a8f";
+  "14.12".us-west-1.x86_64-linux.pv-ebs = "ami-3e534a7b";
+  "14.12".us-west-1.x86_64-linux.pv-s3 = "ami-2905196c";
+  "14.12".us-west-2.x86_64-linux.hvm-ebs = "ami-fb9dc3cb";
+  "14.12".us-west-2.x86_64-linux.pv-ebs = "ami-899dc3b9";
+  "14.12".us-west-2.x86_64-linux.pv-s3 = "ami-cb7f2dfb";
+
+  "15.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-58cac236";
+  "15.09".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-39c8c057";
+  "15.09".ap-northeast-1.x86_64-linux.pv-ebs = "ami-5ac9c134";
+  "15.09".ap-northeast-1.x86_64-linux.pv-s3 = "ami-03cec66d";
+  "15.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-2fc2094c";
+  "15.09".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-9ec308fd";
+  "15.09".ap-southeast-1.x86_64-linux.pv-ebs = "ami-95c00bf6";
+  "15.09".ap-southeast-1.x86_64-linux.pv-s3 = "ami-bfc00bdc";
+  "15.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-996c4cfa";
+  "15.09".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-3f6e4e5c";
+  "15.09".ap-southeast-2.x86_64-linux.pv-ebs = "ami-066d4d65";
+  "15.09".ap-southeast-2.x86_64-linux.pv-s3 = "ami-cc6e4eaf";
+  "15.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-3f8c6b50";
+  "15.09".eu-central-1.x86_64-linux.hvm-s3 = "ami-5b836434";
+  "15.09".eu-central-1.x86_64-linux.pv-ebs = "ami-118c6b7e";
+  "15.09".eu-central-1.x86_64-linux.pv-s3 = "ami-2c977043";
+  "15.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-9cf04aef";
+  "15.09".eu-west-1.x86_64-linux.hvm-s3 = "ami-2bea5058";
+  "15.09".eu-west-1.x86_64-linux.pv-ebs = "ami-c9e852ba";
+  "15.09".eu-west-1.x86_64-linux.pv-s3 = "ami-c6f64cb5";
+  "15.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-6e52df02";
+  "15.09".sa-east-1.x86_64-linux.hvm-s3 = "ami-1852df74";
+  "15.09".sa-east-1.x86_64-linux.pv-ebs = "ami-4368e52f";
+  "15.09".sa-east-1.x86_64-linux.pv-s3 = "ami-f15ad79d";
+  "15.09".us-east-1.x86_64-linux.hvm-ebs = "ami-84a6a0ee";
+  "15.09".us-east-1.x86_64-linux.hvm-s3 = "ami-06a7a16c";
+  "15.09".us-east-1.x86_64-linux.pv-ebs = "ami-a4a1a7ce";
+  "15.09".us-east-1.x86_64-linux.pv-s3 = "ami-5ba8ae31";
+  "15.09".us-west-1.x86_64-linux.hvm-ebs = "ami-22c8bb42";
+  "15.09".us-west-1.x86_64-linux.hvm-s3 = "ami-a2ccbfc2";
+  "15.09".us-west-1.x86_64-linux.pv-ebs = "ami-10cebd70";
+  "15.09".us-west-1.x86_64-linux.pv-s3 = "ami-fa30429a";
+  "15.09".us-west-2.x86_64-linux.hvm-ebs = "ami-ce57b9ae";
+  "15.09".us-west-2.x86_64-linux.hvm-s3 = "ami-2956b849";
+  "15.09".us-west-2.x86_64-linux.pv-ebs = "ami-005fb160";
+  "15.09".us-west-2.x86_64-linux.pv-s3 = "ami-cd55bbad";
+
+  "16.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-40619d21";
+  "16.03".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-ce629eaf";
+  "16.03".ap-northeast-1.x86_64-linux.pv-ebs = "ami-ef639f8e";
+  "16.03".ap-northeast-1.x86_64-linux.pv-s3 = "ami-a1609cc0";
+  "16.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-deca00b0";
+  "16.03".ap-northeast-2.x86_64-linux.hvm-s3 = "ami-a3b77dcd";
+  "16.03".ap-northeast-2.x86_64-linux.pv-ebs = "ami-7bcb0115";
+  "16.03".ap-northeast-2.x86_64-linux.pv-s3 = "ami-a2b77dcc";
+  "16.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-0dff9562";
+  "16.03".ap-south-1.x86_64-linux.hvm-s3 = "ami-13f69c7c";
+  "16.03".ap-south-1.x86_64-linux.pv-ebs = "ami-0ef39961";
+  "16.03".ap-south-1.x86_64-linux.pv-s3 = "ami-e0c8a28f";
+  "16.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-5e964a3d";
+  "16.03".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-4d964a2e";
+  "16.03".ap-southeast-1.x86_64-linux.pv-ebs = "ami-ec9b478f";
+  "16.03".ap-southeast-1.x86_64-linux.pv-s3 = "ami-999b47fa";
+  "16.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-9f7359fc";
+  "16.03".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-987359fb";
+  "16.03".ap-southeast-2.x86_64-linux.pv-ebs = "ami-a2705ac1";
+  "16.03".ap-southeast-2.x86_64-linux.pv-s3 = "ami-a3705ac0";
+  "16.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-17a45178";
+  "16.03".eu-central-1.x86_64-linux.hvm-s3 = "ami-f9a55096";
+  "16.03".eu-central-1.x86_64-linux.pv-ebs = "ami-c8a550a7";
+  "16.03".eu-central-1.x86_64-linux.pv-s3 = "ami-6ea45101";
+  "16.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-b5b3d5c6";
+  "16.03".eu-west-1.x86_64-linux.hvm-s3 = "ami-c986e0ba";
+  "16.03".eu-west-1.x86_64-linux.pv-ebs = "ami-b083e5c3";
+  "16.03".eu-west-1.x86_64-linux.pv-s3 = "ami-3c83e54f";
+  "16.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-f6eb7f9a";
+  "16.03".sa-east-1.x86_64-linux.hvm-s3 = "ami-93e773ff";
+  "16.03".sa-east-1.x86_64-linux.pv-ebs = "ami-cbb82ca7";
+  "16.03".sa-east-1.x86_64-linux.pv-s3 = "ami-abb82cc7";
+  "16.03".us-east-1.x86_64-linux.hvm-ebs = "ami-c123a3d6";
+  "16.03".us-east-1.x86_64-linux.hvm-s3 = "ami-bc25a5ab";
+  "16.03".us-east-1.x86_64-linux.pv-ebs = "ami-bd25a5aa";
+  "16.03".us-east-1.x86_64-linux.pv-s3 = "ami-a325a5b4";
+  "16.03".us-west-1.x86_64-linux.hvm-ebs = "ami-748bcd14";
+  "16.03".us-west-1.x86_64-linux.hvm-s3 = "ami-a68dcbc6";
+  "16.03".us-west-1.x86_64-linux.pv-ebs = "ami-048acc64";
+  "16.03".us-west-1.x86_64-linux.pv-s3 = "ami-208dcb40";
+  "16.03".us-west-2.x86_64-linux.hvm-ebs = "ami-8263a0e2";
+  "16.03".us-west-2.x86_64-linux.hvm-s3 = "ami-925c9ff2";
+  "16.03".us-west-2.x86_64-linux.pv-ebs = "ami-5e61a23e";
+  "16.03".us-west-2.x86_64-linux.pv-s3 = "ami-734c8f13";
+
+  # 16.09.1508.3909827
+  "16.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-68453b0f";
+  "16.09".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-f9bec09e";
+  "16.09".ap-northeast-1.x86_64-linux.pv-ebs = "ami-254a3442";
+  "16.09".ap-northeast-1.x86_64-linux.pv-s3 = "ami-ef473988";
+  "16.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-18ae7f76";
+  "16.09".ap-northeast-2.x86_64-linux.hvm-s3 = "ami-9eac7df0";
+  "16.09".ap-northeast-2.x86_64-linux.pv-ebs = "ami-57aa7b39";
+  "16.09".ap-northeast-2.x86_64-linux.pv-s3 = "ami-5cae7f32";
+  "16.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-b3f98fdc";
+  "16.09".ap-south-1.x86_64-linux.hvm-s3 = "ami-98e690f7";
+  "16.09".ap-south-1.x86_64-linux.pv-ebs = "ami-aef98fc1";
+  "16.09".ap-south-1.x86_64-linux.pv-s3 = "ami-caf88ea5";
+  "16.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-80fb51e3";
+  "16.09".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-2df3594e";
+  "16.09".ap-southeast-1.x86_64-linux.pv-ebs = "ami-37f05a54";
+  "16.09".ap-southeast-1.x86_64-linux.pv-s3 = "ami-27f35944";
+  "16.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-57ece834";
+  "16.09".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-87f4f0e4";
+  "16.09".ap-southeast-2.x86_64-linux.pv-ebs = "ami-d8ede9bb";
+  "16.09".ap-southeast-2.x86_64-linux.pv-s3 = "ami-a6ebefc5";
+  "16.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-9f863bfb";
+  "16.09".ca-central-1.x86_64-linux.hvm-s3 = "ami-ea85388e";
+  "16.09".ca-central-1.x86_64-linux.pv-ebs = "ami-ce8a37aa";
+  "16.09".ca-central-1.x86_64-linux.pv-s3 = "ami-448a3720";
+  "16.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-1b884774";
+  "16.09".eu-central-1.x86_64-linux.hvm-s3 = "ami-b08c43df";
+  "16.09".eu-central-1.x86_64-linux.pv-ebs = "ami-888946e7";
+  "16.09".eu-central-1.x86_64-linux.pv-s3 = "ami-06874869";
+  "16.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-1ed3e76d";
+  "16.09".eu-west-1.x86_64-linux.hvm-s3 = "ami-73d1e500";
+  "16.09".eu-west-1.x86_64-linux.pv-ebs = "ami-44c0f437";
+  "16.09".eu-west-1.x86_64-linux.pv-s3 = "ami-f3d8ec80";
+  "16.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-2c9c9648";
+  "16.09".eu-west-2.x86_64-linux.hvm-s3 = "ami-6b9e940f";
+  "16.09".eu-west-2.x86_64-linux.pv-ebs = "ami-f1999395";
+  "16.09".eu-west-2.x86_64-linux.pv-s3 = "ami-bb9f95df";
+  "16.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-a11882cd";
+  "16.09".sa-east-1.x86_64-linux.hvm-s3 = "ami-7726bc1b";
+  "16.09".sa-east-1.x86_64-linux.pv-ebs = "ami-9725bffb";
+  "16.09".sa-east-1.x86_64-linux.pv-s3 = "ami-b027bddc";
+  "16.09".us-east-1.x86_64-linux.hvm-ebs = "ami-854ca593";
+  "16.09".us-east-1.x86_64-linux.hvm-s3 = "ami-2241a834";
+  "16.09".us-east-1.x86_64-linux.pv-ebs = "ami-a441a8b2";
+  "16.09".us-east-1.x86_64-linux.pv-s3 = "ami-e841a8fe";
+  "16.09".us-east-2.x86_64-linux.hvm-ebs = "ami-3f41645a";
+  "16.09".us-east-2.x86_64-linux.hvm-s3 = "ami-804065e5";
+  "16.09".us-east-2.x86_64-linux.pv-ebs = "ami-f1466394";
+  "16.09".us-east-2.x86_64-linux.pv-s3 = "ami-05426760";
+  "16.09".us-west-1.x86_64-linux.hvm-ebs = "ami-c2efbca2";
+  "16.09".us-west-1.x86_64-linux.hvm-s3 = "ami-d71042b7";
+  "16.09".us-west-1.x86_64-linux.pv-ebs = "ami-04e8bb64";
+  "16.09".us-west-1.x86_64-linux.pv-s3 = "ami-31e9ba51";
+  "16.09".us-west-2.x86_64-linux.hvm-ebs = "ami-6449f504";
+  "16.09".us-west-2.x86_64-linux.hvm-s3 = "ami-344af654";
+  "16.09".us-west-2.x86_64-linux.pv-ebs = "ami-6d4af60d";
+  "16.09".us-west-2.x86_64-linux.pv-s3 = "ami-de48f4be";
+
+  # 17.03.885.6024dd4067
+  "17.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-dbd0f7bc";
+  "17.03".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-7cdff81b";
+  "17.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-c59a48ab";
+  "17.03".ap-northeast-2.x86_64-linux.hvm-s3 = "ami-0b944665";
+  "17.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-4f413220";
+  "17.03".ap-south-1.x86_64-linux.hvm-s3 = "ami-864033e9";
+  "17.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-e08c3383";
+  "17.03".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-c28f30a1";
+  "17.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-fca9a69f";
+  "17.03".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-3daaa55e";
+  "17.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-9b00bdff";
+  "17.03".ca-central-1.x86_64-linux.hvm-s3 = "ami-e800bd8c";
+  "17.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-5450803b";
+  "17.03".eu-central-1.x86_64-linux.hvm-s3 = "ami-6e2efe01";
+  "17.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-10754c76";
+  "17.03".eu-west-1.x86_64-linux.hvm-s3 = "ami-11734a77";
+  "17.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-ff1d099b";
+  "17.03".eu-west-2.x86_64-linux.hvm-s3 = "ami-fe1d099a";
+  "17.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-d95d3eb5";
+  "17.03".sa-east-1.x86_64-linux.hvm-s3 = "ami-fca2c190";
+  "17.03".us-east-1.x86_64-linux.hvm-ebs = "ami-0940c61f";
+  "17.03".us-east-1.x86_64-linux.hvm-s3 = "ami-674fc971";
+  "17.03".us-east-2.x86_64-linux.hvm-ebs = "ami-afc2e6ca";
+  "17.03".us-east-2.x86_64-linux.hvm-s3 = "ami-a1cde9c4";
+  "17.03".us-west-1.x86_64-linux.hvm-ebs = "ami-587b2138";
+  "17.03".us-west-1.x86_64-linux.hvm-s3 = "ami-70411b10";
+  "17.03".us-west-2.x86_64-linux.hvm-ebs = "ami-a93daac9";
+  "17.03".us-west-2.x86_64-linux.hvm-s3 = "ami-5139ae31";
+
+  # 17.09.2681.59661f21be6
+  "17.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-a30192da";
+  "17.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-295a414d";
+  "17.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-8c0eb9f1";
+  "17.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-266cfe49";
+  "17.09".us-east-1.x86_64-linux.hvm-ebs = "ami-40bee63a";
+  "17.09".us-east-2.x86_64-linux.hvm-ebs = "ami-9d84aff8";
+  "17.09".us-west-1.x86_64-linux.hvm-ebs = "ami-d14142b1";
+  "17.09".us-west-2.x86_64-linux.hvm-ebs = "ami-3eb40346";
+  "17.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-ca8207ae";
+  "17.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-84bccff8";
+  "17.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0dc5386f";
+  "17.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-89b921ef";
+  "17.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-179b3b79";
+  "17.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-4762202b";
+  "17.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-4e376021";
+
+  # 18.03.132946.1caae7247b8
+  "18.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-065c46ec";
+  "18.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-64f31903";
+  "18.03".eu-west-3.x86_64-linux.hvm-ebs = "ami-5a8d3d27";
+  "18.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-09faf9e2";
+  "18.03".us-east-1.x86_64-linux.hvm-ebs = "ami-8b3538f4";
+  "18.03".us-east-2.x86_64-linux.hvm-ebs = "ami-150b3170";
+  "18.03".us-west-1.x86_64-linux.hvm-ebs = "ami-ce06ebad";
+  "18.03".us-west-2.x86_64-linux.hvm-ebs = "ami-586c3520";
+  "18.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-aca72ac8";
+  "18.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-aa0b4d40";
+  "18.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-d0f254b2";
+  "18.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-456511a8";
+  "18.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-3366d15d";
+  "18.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-163e1f7a";
+  "18.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-6a390b05";
+
+  # 18.09.910.c15e342304a
+  "18.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-0f412186fb8a0ec97";
+  "18.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-0dada3805ce43c55e";
+  "18.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-074df85565f2e02e2";
+  "18.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-07c9b884e679df4f8";
+  "18.09".us-east-1.x86_64-linux.hvm-ebs = "ami-009c9c3f1af480ff3";
+  "18.09".us-east-2.x86_64-linux.hvm-ebs = "ami-08199961085ea8bc6";
+  "18.09".us-west-1.x86_64-linux.hvm-ebs = "ami-07aa7f56d612ddd38";
+  "18.09".us-west-2.x86_64-linux.hvm-ebs = "ami-01c84b7c368ac24d1";
+  "18.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-04f66113f76198f6c";
+  "18.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0892c7e24ebf2194f";
+  "18.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-010730f36424b0a2c";
+  "18.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-0cdba8e998f076547";
+  "18.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0400a698e6a9f4a15";
+  "18.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-0e4a8a47fd6db6112";
+  "18.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-0880a678d3f555313";
+
+  # 19.03.172286.8ea36d73256
+  "19.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-0fe40176548ff0940";
+  "19.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-03a40fd3a02fe95ba";
+  "19.03".eu-west-3.x86_64-linux.hvm-ebs = "ami-0436f9da0f20a638e";
+  "19.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-0022b8ea9efde5de4";
+  "19.03".us-east-1.x86_64-linux.hvm-ebs = "ami-0efc58fb70ae9a217";
+  "19.03".us-east-2.x86_64-linux.hvm-ebs = "ami-0abf711b1b34da1af";
+  "19.03".us-west-1.x86_64-linux.hvm-ebs = "ami-07d126e8838c40ec5";
+  "19.03".us-west-2.x86_64-linux.hvm-ebs = "ami-03f8a737546e47fb0";
+  "19.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-03f9fd0ef2e035ede";
+  "19.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0cff66114c652c262";
+  "19.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-054c73a7f8d773ea9";
+  "19.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-00db62688900456a4";
+  "19.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0485cdd1a5fdd2117";
+  "19.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
+  "19.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-0303deb1b5890f878";
+
+  # 19.09.2243.84af403f54f
+  "19.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-071082f0fa035374f";
+  "19.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-0d9dc33c54d1dc4c3";
+  "19.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-09566799591d1bfed";
+  "19.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-015f8efc2be419b79";
+  "19.09".eu-north-1.x86_64-linux.hvm-ebs = "ami-07fc0a32d885e01ed";
+  "19.09".us-east-1.x86_64-linux.hvm-ebs = "ami-03330d8b51287412f";
+  "19.09".us-east-2.x86_64-linux.hvm-ebs = "ami-0518b4c84972e967f";
+  "19.09".us-west-1.x86_64-linux.hvm-ebs = "ami-06ad07e61a353b4a6";
+  "19.09".us-west-2.x86_64-linux.hvm-ebs = "ami-0e31e30925cf3ce4e";
+  "19.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-07df50fc76702a36d";
+  "19.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0f71ae5d4b0b78d95";
+  "19.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-057bbf2b4bd62d210";
+  "19.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-02a62555ca182fb5b";
+  "19.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0219dde0e6b7b7b93";
+  "19.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-066f7f2a895c821a1";
+  "19.09".ap-east-1.x86_64-linux.hvm-ebs = "ami-055b2348db2827ff1";
+  "19.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-018aab68377227e06";
+
+  # 20.03.1554.94e39623a49
+  "20.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-02c34db5766cc7013";
+  "20.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-0e32bd8c7853883f1";
+  "20.03".eu-west-3.x86_64-linux.hvm-ebs = "ami-061edb1356c1d69fd";
+  "20.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-0a1a94722dcbff94c";
+  "20.03".eu-north-1.x86_64-linux.hvm-ebs = "ami-02699abfacbb6464b";
+  "20.03".us-east-1.x86_64-linux.hvm-ebs = "ami-0c5e7760748b74e85";
+  "20.03".us-east-2.x86_64-linux.hvm-ebs = "ami-030296bb256764655";
+  "20.03".us-west-1.x86_64-linux.hvm-ebs = "ami-050be818e0266b741";
+  "20.03".us-west-2.x86_64-linux.hvm-ebs = "ami-06562f78dca68eda2";
+  "20.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-02365684a173255c7";
+  "20.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0dbf353e168d155f7";
+  "20.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-04c0f3a75f63daddd";
+  "20.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-093d9cc49c191eb6c";
+  "20.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0087df91a7b6ebd45";
+  "20.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-0a1a6b569af04af9d";
+  "20.03".ap-east-1.x86_64-linux.hvm-ebs = "ami-0d18fdd309cdefa86";
+  "20.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-09859378158ae971d";
+  # 20.03.2351.f8248ab6d9e-aarch64-linux
+  "20.03".eu-west-1.aarch64-linux.hvm-ebs = "ami-0a4c46dfdfe921aab";
+  "20.03".eu-west-2.aarch64-linux.hvm-ebs = "ami-0b47871912b7d36f9";
+  "20.03".eu-west-3.aarch64-linux.hvm-ebs = "ami-01031e1aa505b8935";
+  "20.03".eu-central-1.aarch64-linux.hvm-ebs = "ami-0bb4669de1f477fd1";
+  # missing "20.03".eu-north-1.aarch64-linux.hvm-ebs = "ami-";
+  "20.03".us-east-1.aarch64-linux.hvm-ebs = "ami-01d2de16a1878271c";
+  "20.03".us-east-2.aarch64-linux.hvm-ebs = "ami-0eade0158b1ff49c0";
+  "20.03".us-west-1.aarch64-linux.hvm-ebs = "ami-0913bf30cb9a764a4";
+  "20.03".us-west-2.aarch64-linux.hvm-ebs = "ami-073449580ff8e82b5";
+  "20.03".ca-central-1.aarch64-linux.hvm-ebs = "ami-050f2e923c4d703c0";
+  "20.03".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-0d11ef6705a9a11a7";
+  "20.03".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-05446a2f818cd3263";
+  "20.03".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-0c057f010065d2453";
+  "20.03".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-0e90eda7f24eb33ab";
+  "20.03".ap-south-1.aarch64-linux.hvm-ebs = "ami-03ba7e9f093f568bc";
+  "20.03".sa-east-1.aarch64-linux.hvm-ebs = "ami-0a8344c6ce6d0c902";
+
+  # 20.09.2016.19db3e5ea27
+  "20.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-0057cb7d614329fa2";
+  "20.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-0d46f16e0bb0ec8fd";
+  "20.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-0e8985c3ea42f87fe";
+  "20.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-0eed77c38432886d2";
+  "20.09".eu-north-1.x86_64-linux.hvm-ebs = "ami-0be5bcadd632bea14";
+  "20.09".us-east-1.x86_64-linux.hvm-ebs = "ami-0a2cce52b42daccc8";
+  "20.09".us-east-2.x86_64-linux.hvm-ebs = "ami-09378bf487b07a4d8";
+  "20.09".us-west-1.x86_64-linux.hvm-ebs = "ami-09b4337b2a9e77485";
+  "20.09".us-west-2.x86_64-linux.hvm-ebs = "ami-081d3bb5fbee0a1ac";
+  "20.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-020c24c6c607e7ac7";
+  "20.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-08f648d5db009e67d";
+  "20.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0be390efaccbd40f9";
+  "20.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-0c3311601cbe8f927";
+  "20.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0020146701f4d56cf";
+  "20.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-0117e2bd876bb40d1";
+  "20.09".ap-east-1.x86_64-linux.hvm-ebs = "ami-0c42f97e5b1fda92f";
+  "20.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-021637976b094959d";
+  # 20.09.2016.19db3e5ea27-aarch64-linux
+  "20.09".eu-west-1.aarch64-linux.hvm-ebs = "ami-00a02608ff45ff8f9";
+  "20.09".eu-west-2.aarch64-linux.hvm-ebs = "ami-0e991d0f8dca21e20";
+  "20.09".eu-west-3.aarch64-linux.hvm-ebs = "ami-0d18eec4dc48c6f3b";
+  "20.09".eu-central-1.aarch64-linux.hvm-ebs = "ami-01691f25d08f48c9e";
+  "20.09".eu-north-1.aarch64-linux.hvm-ebs = "ami-09bb5aabe567ec6f4";
+  "20.09".us-east-1.aarch64-linux.hvm-ebs = "ami-0504bd006f9eaae42";
+  "20.09".us-east-2.aarch64-linux.hvm-ebs = "ami-00f0f8f2ab2d695ad";
+  "20.09".us-west-1.aarch64-linux.hvm-ebs = "ami-02d147d2cb992f878";
+  "20.09".us-west-2.aarch64-linux.hvm-ebs = "ami-07f40006cf4d4820e";
+  "20.09".ca-central-1.aarch64-linux.hvm-ebs = "ami-0e5f563919a987894";
+  "20.09".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-083e35d1acecae5c1";
+  "20.09".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-052cdc008b245b067";
+  "20.09".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-05e137f373bd72c0c";
+  "20.09".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-020791fe4c32f851a";
+  "20.09".ap-south-1.aarch64-linux.hvm-ebs = "ami-0285bb96a0f2c3955";
+  "20.09".sa-east-1.aarch64-linux.hvm-ebs = "ami-0a55ab650c32be058";
+
+
+  # 21.05.740.aa576357673
+  "21.05".eu-west-1.x86_64-linux.hvm-ebs = "ami-048dbc738074a3083";
+  "21.05".eu-west-2.x86_64-linux.hvm-ebs = "ami-0234cf81fec68315d";
+  "21.05".eu-west-3.x86_64-linux.hvm-ebs = "ami-020e459baf709107d";
+  "21.05".eu-central-1.x86_64-linux.hvm-ebs = "ami-0857d5d1309ab8b77";
+  "21.05".eu-north-1.x86_64-linux.hvm-ebs = "ami-05403e3ae53d3716f";
+  "21.05".us-east-1.x86_64-linux.hvm-ebs = "ami-0d3002ba40b5b9897";
+  "21.05".us-east-2.x86_64-linux.hvm-ebs = "ami-069a0ca1bde6dea52";
+  "21.05".us-west-1.x86_64-linux.hvm-ebs = "ami-0b415460a84bcf9bc";
+  "21.05".us-west-2.x86_64-linux.hvm-ebs = "ami-093cba49754abd7f8";
+  "21.05".ca-central-1.x86_64-linux.hvm-ebs = "ami-065c13e1d52d60b33";
+  "21.05".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-04f570c70ff9b665e";
+  "21.05".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-02a3d1df595df5ef6";
+  "21.05".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-027836fddb5c56012";
+  "21.05".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0edacd41dc7700c39";
+  "21.05".ap-south-1.x86_64-linux.hvm-ebs = "ami-0b279b5bb55288059";
+  "21.05".ap-east-1.x86_64-linux.hvm-ebs = "ami-06dc98082bc55c1fc";
+  "21.05".sa-east-1.x86_64-linux.hvm-ebs = "ami-04737dd49b98936c6";
+
+  # 21.11.333823.96b4157790f-x86_64-linux
+  "21.11".eu-west-1.x86_64-linux.hvm-ebs = "ami-01d0304a712f2f3f0";
+  "21.11".eu-west-2.x86_64-linux.hvm-ebs = "ami-00e828bfc1e5d09ac";
+  "21.11".eu-west-3.x86_64-linux.hvm-ebs = "ami-0e1ea64430d8103f2";
+  "21.11".eu-central-1.x86_64-linux.hvm-ebs = "ami-0fcf28c07e86142c5";
+  "21.11".eu-north-1.x86_64-linux.hvm-ebs = "ami-0ee83a3c6590fd6b1";
+  "21.11".us-east-1.x86_64-linux.hvm-ebs = "ami-099756bfda4540da0";
+  "21.11".us-east-2.x86_64-linux.hvm-ebs = "ami-0b20a80b82052d23f";
+  "21.11".us-west-1.x86_64-linux.hvm-ebs = "ami-088ea590004b01752";
+  "21.11".us-west-2.x86_64-linux.hvm-ebs = "ami-0025b9d4831b911a7";
+  "21.11".ca-central-1.x86_64-linux.hvm-ebs = "ami-0e67089f898e74443";
+  "21.11".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0dc8d718279d3402d";
+  "21.11".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0155e842329970187";
+  "21.11".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-07c95eda953bf5435";
+  "21.11".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-04167df3cd952b3bd";
+  "21.11".ap-south-1.x86_64-linux.hvm-ebs = "ami-0680e05531b3db677";
+  "21.11".ap-east-1.x86_64-linux.hvm-ebs = "ami-0835a3e481dc240f9";
+  "21.11".sa-east-1.x86_64-linux.hvm-ebs = "ami-0f7c354c421348e51";
+
+  # 21.11.333823.96b4157790f-aarch64-linux
+  "21.11".eu-west-1.aarch64-linux.hvm-ebs = "ami-048f3eea6a12c4b3b";
+  "21.11".eu-west-2.aarch64-linux.hvm-ebs = "ami-0e6f18f2009806add";
+  "21.11".eu-west-3.aarch64-linux.hvm-ebs = "ami-0a28d593f5e938d80";
+  "21.11".eu-central-1.aarch64-linux.hvm-ebs = "ami-0b9c95d926ab9474c";
+  "21.11".eu-north-1.aarch64-linux.hvm-ebs = "ami-0f2d400b4a2368a1a";
+  "21.11".us-east-1.aarch64-linux.hvm-ebs = "ami-05afb75585567d386";
+  "21.11".us-east-2.aarch64-linux.hvm-ebs = "ami-07f360673c2fccf8d";
+  "21.11".us-west-1.aarch64-linux.hvm-ebs = "ami-0a6892c61d85774db";
+  "21.11".us-west-2.aarch64-linux.hvm-ebs = "ami-04eaf20283432e852";
+  "21.11".ca-central-1.aarch64-linux.hvm-ebs = "ami-036b69828502e7fdf";
+  "21.11".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-0d52e51e68b6954ef";
+  "21.11".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-000a3019e003f4fb9";
+  "21.11".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-09b0c7928780e25b6";
+  "21.11".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-05f80f3c83083ff62";
+  "21.11".ap-south-1.aarch64-linux.hvm-ebs = "ami-05b2a3ff8489c3f59";
+  "21.11".ap-east-1.aarch64-linux.hvm-ebs = "ami-0aa3b50a4f2822a00";
+  "21.11".sa-east-1.aarch64-linux.hvm-ebs = "ami-00f68eff453d3fe69";
+
+  latest = self."21.11";
+}; in self
diff --git a/nixpkgs/nixos/modules/virtualisation/amazon-options.nix b/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
index 698edcd835a6..0465571ca926 100644
--- a/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
+++ b/nixpkgs/nixos/modules/virtualisation/amazon-options.nix
@@ -1,6 +1,6 @@
 { config, lib, pkgs, ... }:
 let
-  inherit (lib) types;
+  inherit (lib) literalExpression types;
 in {
   options = {
     ec2 = {
@@ -50,6 +50,7 @@ in {
       };
       efi = lib.mkOption {
         default = pkgs.stdenv.hostPlatform.isAarch64;
+        defaultText = literalExpression "pkgs.stdenv.hostPlatform.isAarch64";
         internal = true;
         description = ''
           Whether the EC2 instance is using EFI.
diff --git a/nixpkgs/nixos/modules/virtualisation/azure-agent.nix b/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
index 41f3fa0e6642..bd8c7f8c1eea 100644
--- a/nixpkgs/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/virtualisation/cri-o.nix b/nixpkgs/nixos/modules/virtualisation/cri-o.nix
index 38766113f391..cf5110001503 100644
--- a/nixpkgs/nixos/modules/virtualisation/cri-o.nix
+++ b/nixpkgs/nixos/modules/virtualisation/cri-o.nix
@@ -71,6 +71,10 @@ in
     package = mkOption {
       type = types.package;
       default = crioPackage;
+      defaultText = literalDocBook ''
+        <literal>pkgs.cri-o</literal> built with
+        <literal>config.${opt.extraPackages}</literal>.
+      '';
       internal = true;
       description = ''
         The final CRI-O package (including extra packages).
diff --git a/nixpkgs/nixos/modules/virtualisation/docker-rootless.nix b/nixpkgs/nixos/modules/virtualisation/docker-rootless.nix
new file mode 100644
index 000000000000..0e7f05031420
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/docker-rootless.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.virtualisation.docker.rootless;
+  proxy_env = config.networking.proxy.envVars;
+  settingsFormat = pkgs.formats.json {};
+  daemonSettingsFile = settingsFormat.generate "daemon.json" cfg.daemon.settings;
+
+in
+
+{
+  ###### interface
+
+  options.virtualisation.docker.rootless = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        This option enables docker in a rootless mode, a daemon that manages
+        linux containers. To interact with the daemon, one needs to set
+        <command>DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock</command>.
+      '';
+    };
+
+    setSocketVariable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Point <command>DOCKER_HOST</command> to rootless Docker instance for
+        normal users by default.
+      '';
+    };
+
+    daemon.settings = mkOption {
+      type = settingsFormat.type;
+      default = { };
+      example = {
+        ipv6 = true;
+        "fixed-cidr-v6" = "fd00::/80";
+      };
+      description = ''
+        Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
+        See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.docker;
+      defaultText = literalExpression "pkgs.docker";
+      type = types.package;
+      example = literalExpression "pkgs.docker-edge";
+      description = ''
+        Docker package to be used in the module.
+      '';
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    environment.extraInit = optionalString cfg.setSocketVariable ''
+      if [ -z "$DOCKER_HOST" -a -n "$XDG_RUNTIME_DIR" ]; then
+        export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"
+      fi
+    '';
+
+    # Taken from https://github.com/moby/moby/blob/master/contrib/dockerd-rootless-setuptool.sh
+    systemd.user.services.docker = {
+      wantedBy = [ "default.target" ];
+      description = "Docker Application Container Engine (Rootless)";
+      # needs newuidmap from pkgs.shadow
+      path = [ "/run/wrappers" ];
+      environment = proxy_env;
+      unitConfig.StartLimitInterval = "60s";
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${cfg.package}/bin/dockerd-rootless --config-file=${daemonSettingsFile}";
+        ExecReload = "${pkgs.procps}/bin/kill -s HUP $MAINPID";
+        TimeoutSec = 0;
+        RestartSec = 2;
+        Restart = "always";
+        StartLimitBurst = 3;
+        LimitNOFILE = "infinity";
+        LimitNPROC = "infinity";
+        LimitCORE = "infinity";
+        Delegate = true;
+        NotifyAccess = "all";
+        KillMode = "mixed";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/docker.nix b/nixpkgs/nixos/modules/virtualisation/docker.nix
index 06858e150309..a69cbe55c784 100644
--- a/nixpkgs/nixos/modules/virtualisation/docker.nix
+++ b/nixpkgs/nixos/modules/virtualisation/docker.nix
@@ -8,7 +8,8 @@ let
 
   cfg = config.virtualisation.docker;
   proxy_env = config.networking.proxy.envVars;
-
+  settingsFormat = pkgs.formats.json {};
+  daemonSettingsFile = settingsFormat.generate "daemon.json" cfg.daemon.settings;
 in
 
 {
@@ -52,6 +53,20 @@ in
           '';
       };
 
+    daemon.settings =
+      mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = {
+          ipv6 = true;
+          "fixed-cidr-v6" = "fd00::/80";
+        };
+        description = ''
+          Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
+          See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
+        '';
+      };
+
     enableNvidia =
       mkOption {
         type = types.bool;
@@ -171,12 +186,7 @@ in
             ""
             ''
               ${cfg.package}/bin/dockerd \
-                --group=docker \
-                --host=fd:// \
-                --log-driver=${cfg.logDriver} \
-                ${optionalString (cfg.storageDriver != null) "--storage-driver=${cfg.storageDriver}"} \
-                ${optionalString cfg.liveRestore "--live-restore" } \
-                ${optionalString cfg.enableNvidia "--add-runtime nvidia=${pkgs.nvidia-docker}/bin/nvidia-container-runtime" } \
+                --config-file=${daemonSettingsFile} \
                 ${cfg.extraOptions}
             ''];
           ExecReload=[
@@ -219,6 +229,19 @@ in
         { assertion = cfg.enableNvidia -> config.hardware.opengl.driSupport32Bit or false;
           message = "Option enableNvidia requires 32bit support libraries";
         }];
+
+      virtualisation.docker.daemon.settings = {
+        group = "docker";
+        hosts = [ "fd://" ];
+        log-driver = mkDefault cfg.logDriver;
+        storage-driver = mkIf (cfg.storageDriver != null) (mkDefault cfg.storageDriver);
+        live-restore = mkDefault cfg.liveRestore;
+        runtimes = mkIf cfg.enableNvidia {
+          nvidia = {
+            path = "${pkgs.nvidia-docker}/bin/nvidia-container-runtime";
+          };
+        };
+      };
     }
   ]);
 
diff --git a/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix b/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix
index d38f41ab39d7..1ffb326ba7a8 100644
--- a/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix
+++ b/nixpkgs/nixos/modules/virtualisation/ec2-amis.nix
@@ -1,371 +1,9 @@
-let self = {
-  "14.04".ap-northeast-1.hvm-ebs = "ami-71c6f470";
-  "14.04".ap-northeast-1.pv-ebs = "ami-4dcbf84c";
-  "14.04".ap-northeast-1.pv-s3 = "ami-8fc4f68e";
-  "14.04".ap-southeast-1.hvm-ebs = "ami-da280888";
-  "14.04".ap-southeast-1.pv-ebs = "ami-7a9dbc28";
-  "14.04".ap-southeast-1.pv-s3 = "ami-c4290996";
-  "14.04".ap-southeast-2.hvm-ebs = "ami-ab523e91";
-  "14.04".ap-southeast-2.pv-ebs = "ami-6769055d";
-  "14.04".ap-southeast-2.pv-s3 = "ami-15533f2f";
-  "14.04".eu-central-1.hvm-ebs = "ami-ba0234a7";
-  "14.04".eu-west-1.hvm-ebs = "ami-96cb63e1";
-  "14.04".eu-west-1.pv-ebs = "ami-b48c25c3";
-  "14.04".eu-west-1.pv-s3 = "ami-06cd6571";
-  "14.04".sa-east-1.hvm-ebs = "ami-01b90e1c";
-  "14.04".sa-east-1.pv-ebs = "ami-69e35474";
-  "14.04".sa-east-1.pv-s3 = "ami-61b90e7c";
-  "14.04".us-east-1.hvm-ebs = "ami-58ba3a30";
-  "14.04".us-east-1.pv-ebs = "ami-9e0583f6";
-  "14.04".us-east-1.pv-s3 = "ami-9cbe3ef4";
-  "14.04".us-west-1.hvm-ebs = "ami-0bc3d74e";
-  "14.04".us-west-1.pv-ebs = "ami-8b1703ce";
-  "14.04".us-west-1.pv-s3 = "ami-27ccd862";
-  "14.04".us-west-2.hvm-ebs = "ami-3bf1bf0b";
-  "14.04".us-west-2.pv-ebs = "ami-259bd515";
-  "14.04".us-west-2.pv-s3 = "ami-07094037";
-
-  "14.12".ap-northeast-1.hvm-ebs = "ami-24435f25";
-  "14.12".ap-northeast-1.pv-ebs = "ami-b0425eb1";
-  "14.12".ap-northeast-1.pv-s3 = "ami-fed3c6ff";
-  "14.12".ap-southeast-1.hvm-ebs = "ami-6c765d3e";
-  "14.12".ap-southeast-1.pv-ebs = "ami-6a765d38";
-  "14.12".ap-southeast-1.pv-s3 = "ami-d1bf9183";
-  "14.12".ap-southeast-2.hvm-ebs = "ami-af86f395";
-  "14.12".ap-southeast-2.pv-ebs = "ami-b386f389";
-  "14.12".ap-southeast-2.pv-s3 = "ami-69c5ae53";
-  "14.12".eu-central-1.hvm-ebs = "ami-4a497a57";
-  "14.12".eu-central-1.pv-ebs = "ami-4c497a51";
-  "14.12".eu-central-1.pv-s3 = "ami-60f2c27d";
-  "14.12".eu-west-1.hvm-ebs = "ami-d126a5a6";
-  "14.12".eu-west-1.pv-ebs = "ami-0126a576";
-  "14.12".eu-west-1.pv-s3 = "ami-deda5fa9";
-  "14.12".sa-east-1.hvm-ebs = "ami-2d239e30";
-  "14.12".sa-east-1.pv-ebs = "ami-35239e28";
-  "14.12".sa-east-1.pv-s3 = "ami-81e3519c";
-  "14.12".us-east-1.hvm-ebs = "ami-0c463a64";
-  "14.12".us-east-1.pv-ebs = "ami-ac473bc4";
-  "14.12".us-east-1.pv-s3 = "ami-00e18a68";
-  "14.12".us-west-1.hvm-ebs = "ami-ca534a8f";
-  "14.12".us-west-1.pv-ebs = "ami-3e534a7b";
-  "14.12".us-west-1.pv-s3 = "ami-2905196c";
-  "14.12".us-west-2.hvm-ebs = "ami-fb9dc3cb";
-  "14.12".us-west-2.pv-ebs = "ami-899dc3b9";
-  "14.12".us-west-2.pv-s3 = "ami-cb7f2dfb";
-
-  "15.09".ap-northeast-1.hvm-ebs = "ami-58cac236";
-  "15.09".ap-northeast-1.hvm-s3 = "ami-39c8c057";
-  "15.09".ap-northeast-1.pv-ebs = "ami-5ac9c134";
-  "15.09".ap-northeast-1.pv-s3 = "ami-03cec66d";
-  "15.09".ap-southeast-1.hvm-ebs = "ami-2fc2094c";
-  "15.09".ap-southeast-1.hvm-s3 = "ami-9ec308fd";
-  "15.09".ap-southeast-1.pv-ebs = "ami-95c00bf6";
-  "15.09".ap-southeast-1.pv-s3 = "ami-bfc00bdc";
-  "15.09".ap-southeast-2.hvm-ebs = "ami-996c4cfa";
-  "15.09".ap-southeast-2.hvm-s3 = "ami-3f6e4e5c";
-  "15.09".ap-southeast-2.pv-ebs = "ami-066d4d65";
-  "15.09".ap-southeast-2.pv-s3 = "ami-cc6e4eaf";
-  "15.09".eu-central-1.hvm-ebs = "ami-3f8c6b50";
-  "15.09".eu-central-1.hvm-s3 = "ami-5b836434";
-  "15.09".eu-central-1.pv-ebs = "ami-118c6b7e";
-  "15.09".eu-central-1.pv-s3 = "ami-2c977043";
-  "15.09".eu-west-1.hvm-ebs = "ami-9cf04aef";
-  "15.09".eu-west-1.hvm-s3 = "ami-2bea5058";
-  "15.09".eu-west-1.pv-ebs = "ami-c9e852ba";
-  "15.09".eu-west-1.pv-s3 = "ami-c6f64cb5";
-  "15.09".sa-east-1.hvm-ebs = "ami-6e52df02";
-  "15.09".sa-east-1.hvm-s3 = "ami-1852df74";
-  "15.09".sa-east-1.pv-ebs = "ami-4368e52f";
-  "15.09".sa-east-1.pv-s3 = "ami-f15ad79d";
-  "15.09".us-east-1.hvm-ebs = "ami-84a6a0ee";
-  "15.09".us-east-1.hvm-s3 = "ami-06a7a16c";
-  "15.09".us-east-1.pv-ebs = "ami-a4a1a7ce";
-  "15.09".us-east-1.pv-s3 = "ami-5ba8ae31";
-  "15.09".us-west-1.hvm-ebs = "ami-22c8bb42";
-  "15.09".us-west-1.hvm-s3 = "ami-a2ccbfc2";
-  "15.09".us-west-1.pv-ebs = "ami-10cebd70";
-  "15.09".us-west-1.pv-s3 = "ami-fa30429a";
-  "15.09".us-west-2.hvm-ebs = "ami-ce57b9ae";
-  "15.09".us-west-2.hvm-s3 = "ami-2956b849";
-  "15.09".us-west-2.pv-ebs = "ami-005fb160";
-  "15.09".us-west-2.pv-s3 = "ami-cd55bbad";
-
-  "16.03".ap-northeast-1.hvm-ebs = "ami-40619d21";
-  "16.03".ap-northeast-1.hvm-s3 = "ami-ce629eaf";
-  "16.03".ap-northeast-1.pv-ebs = "ami-ef639f8e";
-  "16.03".ap-northeast-1.pv-s3 = "ami-a1609cc0";
-  "16.03".ap-northeast-2.hvm-ebs = "ami-deca00b0";
-  "16.03".ap-northeast-2.hvm-s3 = "ami-a3b77dcd";
-  "16.03".ap-northeast-2.pv-ebs = "ami-7bcb0115";
-  "16.03".ap-northeast-2.pv-s3 = "ami-a2b77dcc";
-  "16.03".ap-south-1.hvm-ebs = "ami-0dff9562";
-  "16.03".ap-south-1.hvm-s3 = "ami-13f69c7c";
-  "16.03".ap-south-1.pv-ebs = "ami-0ef39961";
-  "16.03".ap-south-1.pv-s3 = "ami-e0c8a28f";
-  "16.03".ap-southeast-1.hvm-ebs = "ami-5e964a3d";
-  "16.03".ap-southeast-1.hvm-s3 = "ami-4d964a2e";
-  "16.03".ap-southeast-1.pv-ebs = "ami-ec9b478f";
-  "16.03".ap-southeast-1.pv-s3 = "ami-999b47fa";
-  "16.03".ap-southeast-2.hvm-ebs = "ami-9f7359fc";
-  "16.03".ap-southeast-2.hvm-s3 = "ami-987359fb";
-  "16.03".ap-southeast-2.pv-ebs = "ami-a2705ac1";
-  "16.03".ap-southeast-2.pv-s3 = "ami-a3705ac0";
-  "16.03".eu-central-1.hvm-ebs = "ami-17a45178";
-  "16.03".eu-central-1.hvm-s3 = "ami-f9a55096";
-  "16.03".eu-central-1.pv-ebs = "ami-c8a550a7";
-  "16.03".eu-central-1.pv-s3 = "ami-6ea45101";
-  "16.03".eu-west-1.hvm-ebs = "ami-b5b3d5c6";
-  "16.03".eu-west-1.hvm-s3 = "ami-c986e0ba";
-  "16.03".eu-west-1.pv-ebs = "ami-b083e5c3";
-  "16.03".eu-west-1.pv-s3 = "ami-3c83e54f";
-  "16.03".sa-east-1.hvm-ebs = "ami-f6eb7f9a";
-  "16.03".sa-east-1.hvm-s3 = "ami-93e773ff";
-  "16.03".sa-east-1.pv-ebs = "ami-cbb82ca7";
-  "16.03".sa-east-1.pv-s3 = "ami-abb82cc7";
-  "16.03".us-east-1.hvm-ebs = "ami-c123a3d6";
-  "16.03".us-east-1.hvm-s3 = "ami-bc25a5ab";
-  "16.03".us-east-1.pv-ebs = "ami-bd25a5aa";
-  "16.03".us-east-1.pv-s3 = "ami-a325a5b4";
-  "16.03".us-west-1.hvm-ebs = "ami-748bcd14";
-  "16.03".us-west-1.hvm-s3 = "ami-a68dcbc6";
-  "16.03".us-west-1.pv-ebs = "ami-048acc64";
-  "16.03".us-west-1.pv-s3 = "ami-208dcb40";
-  "16.03".us-west-2.hvm-ebs = "ami-8263a0e2";
-  "16.03".us-west-2.hvm-s3 = "ami-925c9ff2";
-  "16.03".us-west-2.pv-ebs = "ami-5e61a23e";
-  "16.03".us-west-2.pv-s3 = "ami-734c8f13";
-
-  # 16.09.1508.3909827
-  "16.09".ap-northeast-1.hvm-ebs = "ami-68453b0f";
-  "16.09".ap-northeast-1.hvm-s3 = "ami-f9bec09e";
-  "16.09".ap-northeast-1.pv-ebs = "ami-254a3442";
-  "16.09".ap-northeast-1.pv-s3 = "ami-ef473988";
-  "16.09".ap-northeast-2.hvm-ebs = "ami-18ae7f76";
-  "16.09".ap-northeast-2.hvm-s3 = "ami-9eac7df0";
-  "16.09".ap-northeast-2.pv-ebs = "ami-57aa7b39";
-  "16.09".ap-northeast-2.pv-s3 = "ami-5cae7f32";
-  "16.09".ap-south-1.hvm-ebs = "ami-b3f98fdc";
-  "16.09".ap-south-1.hvm-s3 = "ami-98e690f7";
-  "16.09".ap-south-1.pv-ebs = "ami-aef98fc1";
-  "16.09".ap-south-1.pv-s3 = "ami-caf88ea5";
-  "16.09".ap-southeast-1.hvm-ebs = "ami-80fb51e3";
-  "16.09".ap-southeast-1.hvm-s3 = "ami-2df3594e";
-  "16.09".ap-southeast-1.pv-ebs = "ami-37f05a54";
-  "16.09".ap-southeast-1.pv-s3 = "ami-27f35944";
-  "16.09".ap-southeast-2.hvm-ebs = "ami-57ece834";
-  "16.09".ap-southeast-2.hvm-s3 = "ami-87f4f0e4";
-  "16.09".ap-southeast-2.pv-ebs = "ami-d8ede9bb";
-  "16.09".ap-southeast-2.pv-s3 = "ami-a6ebefc5";
-  "16.09".ca-central-1.hvm-ebs = "ami-9f863bfb";
-  "16.09".ca-central-1.hvm-s3 = "ami-ea85388e";
-  "16.09".ca-central-1.pv-ebs = "ami-ce8a37aa";
-  "16.09".ca-central-1.pv-s3 = "ami-448a3720";
-  "16.09".eu-central-1.hvm-ebs = "ami-1b884774";
-  "16.09".eu-central-1.hvm-s3 = "ami-b08c43df";
-  "16.09".eu-central-1.pv-ebs = "ami-888946e7";
-  "16.09".eu-central-1.pv-s3 = "ami-06874869";
-  "16.09".eu-west-1.hvm-ebs = "ami-1ed3e76d";
-  "16.09".eu-west-1.hvm-s3 = "ami-73d1e500";
-  "16.09".eu-west-1.pv-ebs = "ami-44c0f437";
-  "16.09".eu-west-1.pv-s3 = "ami-f3d8ec80";
-  "16.09".eu-west-2.hvm-ebs = "ami-2c9c9648";
-  "16.09".eu-west-2.hvm-s3 = "ami-6b9e940f";
-  "16.09".eu-west-2.pv-ebs = "ami-f1999395";
-  "16.09".eu-west-2.pv-s3 = "ami-bb9f95df";
-  "16.09".sa-east-1.hvm-ebs = "ami-a11882cd";
-  "16.09".sa-east-1.hvm-s3 = "ami-7726bc1b";
-  "16.09".sa-east-1.pv-ebs = "ami-9725bffb";
-  "16.09".sa-east-1.pv-s3 = "ami-b027bddc";
-  "16.09".us-east-1.hvm-ebs = "ami-854ca593";
-  "16.09".us-east-1.hvm-s3 = "ami-2241a834";
-  "16.09".us-east-1.pv-ebs = "ami-a441a8b2";
-  "16.09".us-east-1.pv-s3 = "ami-e841a8fe";
-  "16.09".us-east-2.hvm-ebs = "ami-3f41645a";
-  "16.09".us-east-2.hvm-s3 = "ami-804065e5";
-  "16.09".us-east-2.pv-ebs = "ami-f1466394";
-  "16.09".us-east-2.pv-s3 = "ami-05426760";
-  "16.09".us-west-1.hvm-ebs = "ami-c2efbca2";
-  "16.09".us-west-1.hvm-s3 = "ami-d71042b7";
-  "16.09".us-west-1.pv-ebs = "ami-04e8bb64";
-  "16.09".us-west-1.pv-s3 = "ami-31e9ba51";
-  "16.09".us-west-2.hvm-ebs = "ami-6449f504";
-  "16.09".us-west-2.hvm-s3 = "ami-344af654";
-  "16.09".us-west-2.pv-ebs = "ami-6d4af60d";
-  "16.09".us-west-2.pv-s3 = "ami-de48f4be";
-
-  # 17.03.885.6024dd4067
-  "17.03".ap-northeast-1.hvm-ebs = "ami-dbd0f7bc";
-  "17.03".ap-northeast-1.hvm-s3 = "ami-7cdff81b";
-  "17.03".ap-northeast-2.hvm-ebs = "ami-c59a48ab";
-  "17.03".ap-northeast-2.hvm-s3 = "ami-0b944665";
-  "17.03".ap-south-1.hvm-ebs = "ami-4f413220";
-  "17.03".ap-south-1.hvm-s3 = "ami-864033e9";
-  "17.03".ap-southeast-1.hvm-ebs = "ami-e08c3383";
-  "17.03".ap-southeast-1.hvm-s3 = "ami-c28f30a1";
-  "17.03".ap-southeast-2.hvm-ebs = "ami-fca9a69f";
-  "17.03".ap-southeast-2.hvm-s3 = "ami-3daaa55e";
-  "17.03".ca-central-1.hvm-ebs = "ami-9b00bdff";
-  "17.03".ca-central-1.hvm-s3 = "ami-e800bd8c";
-  "17.03".eu-central-1.hvm-ebs = "ami-5450803b";
-  "17.03".eu-central-1.hvm-s3 = "ami-6e2efe01";
-  "17.03".eu-west-1.hvm-ebs = "ami-10754c76";
-  "17.03".eu-west-1.hvm-s3 = "ami-11734a77";
-  "17.03".eu-west-2.hvm-ebs = "ami-ff1d099b";
-  "17.03".eu-west-2.hvm-s3 = "ami-fe1d099a";
-  "17.03".sa-east-1.hvm-ebs = "ami-d95d3eb5";
-  "17.03".sa-east-1.hvm-s3 = "ami-fca2c190";
-  "17.03".us-east-1.hvm-ebs = "ami-0940c61f";
-  "17.03".us-east-1.hvm-s3 = "ami-674fc971";
-  "17.03".us-east-2.hvm-ebs = "ami-afc2e6ca";
-  "17.03".us-east-2.hvm-s3 = "ami-a1cde9c4";
-  "17.03".us-west-1.hvm-ebs = "ami-587b2138";
-  "17.03".us-west-1.hvm-s3 = "ami-70411b10";
-  "17.03".us-west-2.hvm-ebs = "ami-a93daac9";
-  "17.03".us-west-2.hvm-s3 = "ami-5139ae31";
-
-  # 17.09.2681.59661f21be6
-  "17.09".eu-west-1.hvm-ebs = "ami-a30192da";
-  "17.09".eu-west-2.hvm-ebs = "ami-295a414d";
-  "17.09".eu-west-3.hvm-ebs = "ami-8c0eb9f1";
-  "17.09".eu-central-1.hvm-ebs = "ami-266cfe49";
-  "17.09".us-east-1.hvm-ebs = "ami-40bee63a";
-  "17.09".us-east-2.hvm-ebs = "ami-9d84aff8";
-  "17.09".us-west-1.hvm-ebs = "ami-d14142b1";
-  "17.09".us-west-2.hvm-ebs = "ami-3eb40346";
-  "17.09".ca-central-1.hvm-ebs = "ami-ca8207ae";
-  "17.09".ap-southeast-1.hvm-ebs = "ami-84bccff8";
-  "17.09".ap-southeast-2.hvm-ebs = "ami-0dc5386f";
-  "17.09".ap-northeast-1.hvm-ebs = "ami-89b921ef";
-  "17.09".ap-northeast-2.hvm-ebs = "ami-179b3b79";
-  "17.09".sa-east-1.hvm-ebs = "ami-4762202b";
-  "17.09".ap-south-1.hvm-ebs = "ami-4e376021";
-
-  # 18.03.132946.1caae7247b8
-  "18.03".eu-west-1.hvm-ebs = "ami-065c46ec";
-  "18.03".eu-west-2.hvm-ebs = "ami-64f31903";
-  "18.03".eu-west-3.hvm-ebs = "ami-5a8d3d27";
-  "18.03".eu-central-1.hvm-ebs = "ami-09faf9e2";
-  "18.03".us-east-1.hvm-ebs = "ami-8b3538f4";
-  "18.03".us-east-2.hvm-ebs = "ami-150b3170";
-  "18.03".us-west-1.hvm-ebs = "ami-ce06ebad";
-  "18.03".us-west-2.hvm-ebs = "ami-586c3520";
-  "18.03".ca-central-1.hvm-ebs = "ami-aca72ac8";
-  "18.03".ap-southeast-1.hvm-ebs = "ami-aa0b4d40";
-  "18.03".ap-southeast-2.hvm-ebs = "ami-d0f254b2";
-  "18.03".ap-northeast-1.hvm-ebs = "ami-456511a8";
-  "18.03".ap-northeast-2.hvm-ebs = "ami-3366d15d";
-  "18.03".sa-east-1.hvm-ebs = "ami-163e1f7a";
-  "18.03".ap-south-1.hvm-ebs = "ami-6a390b05";
-
-  # 18.09.910.c15e342304a
-  "18.09".eu-west-1.hvm-ebs = "ami-0f412186fb8a0ec97";
-  "18.09".eu-west-2.hvm-ebs = "ami-0dada3805ce43c55e";
-  "18.09".eu-west-3.hvm-ebs = "ami-074df85565f2e02e2";
-  "18.09".eu-central-1.hvm-ebs = "ami-07c9b884e679df4f8";
-  "18.09".us-east-1.hvm-ebs = "ami-009c9c3f1af480ff3";
-  "18.09".us-east-2.hvm-ebs = "ami-08199961085ea8bc6";
-  "18.09".us-west-1.hvm-ebs = "ami-07aa7f56d612ddd38";
-  "18.09".us-west-2.hvm-ebs = "ami-01c84b7c368ac24d1";
-  "18.09".ca-central-1.hvm-ebs = "ami-04f66113f76198f6c";
-  "18.09".ap-southeast-1.hvm-ebs = "ami-0892c7e24ebf2194f";
-  "18.09".ap-southeast-2.hvm-ebs = "ami-010730f36424b0a2c";
-  "18.09".ap-northeast-1.hvm-ebs = "ami-0cdba8e998f076547";
-  "18.09".ap-northeast-2.hvm-ebs = "ami-0400a698e6a9f4a15";
-  "18.09".sa-east-1.hvm-ebs = "ami-0e4a8a47fd6db6112";
-  "18.09".ap-south-1.hvm-ebs = "ami-0880a678d3f555313";
-
-  # 19.03.172286.8ea36d73256
-  "19.03".eu-west-1.hvm-ebs = "ami-0fe40176548ff0940";
-  "19.03".eu-west-2.hvm-ebs = "ami-03a40fd3a02fe95ba";
-  "19.03".eu-west-3.hvm-ebs = "ami-0436f9da0f20a638e";
-  "19.03".eu-central-1.hvm-ebs = "ami-0022b8ea9efde5de4";
-  "19.03".us-east-1.hvm-ebs = "ami-0efc58fb70ae9a217";
-  "19.03".us-east-2.hvm-ebs = "ami-0abf711b1b34da1af";
-  "19.03".us-west-1.hvm-ebs = "ami-07d126e8838c40ec5";
-  "19.03".us-west-2.hvm-ebs = "ami-03f8a737546e47fb0";
-  "19.03".ca-central-1.hvm-ebs = "ami-03f9fd0ef2e035ede";
-  "19.03".ap-southeast-1.hvm-ebs = "ami-0cff66114c652c262";
-  "19.03".ap-southeast-2.hvm-ebs = "ami-054c73a7f8d773ea9";
-  "19.03".ap-northeast-1.hvm-ebs = "ami-00db62688900456a4";
-  "19.03".ap-northeast-2.hvm-ebs = "ami-0485cdd1a5fdd2117";
-  "19.03".sa-east-1.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
-  "19.03".ap-south-1.hvm-ebs = "ami-0303deb1b5890f878";
-
-  # 19.09.2243.84af403f54f
-  "19.09".eu-west-1.hvm-ebs = "ami-071082f0fa035374f";
-  "19.09".eu-west-2.hvm-ebs = "ami-0d9dc33c54d1dc4c3";
-  "19.09".eu-west-3.hvm-ebs = "ami-09566799591d1bfed";
-  "19.09".eu-central-1.hvm-ebs = "ami-015f8efc2be419b79";
-  "19.09".eu-north-1.hvm-ebs = "ami-07fc0a32d885e01ed";
-  "19.09".us-east-1.hvm-ebs = "ami-03330d8b51287412f";
-  "19.09".us-east-2.hvm-ebs = "ami-0518b4c84972e967f";
-  "19.09".us-west-1.hvm-ebs = "ami-06ad07e61a353b4a6";
-  "19.09".us-west-2.hvm-ebs = "ami-0e31e30925cf3ce4e";
-  "19.09".ca-central-1.hvm-ebs = "ami-07df50fc76702a36d";
-  "19.09".ap-southeast-1.hvm-ebs = "ami-0f71ae5d4b0b78d95";
-  "19.09".ap-southeast-2.hvm-ebs = "ami-057bbf2b4bd62d210";
-  "19.09".ap-northeast-1.hvm-ebs = "ami-02a62555ca182fb5b";
-  "19.09".ap-northeast-2.hvm-ebs = "ami-0219dde0e6b7b7b93";
-  "19.09".ap-south-1.hvm-ebs = "ami-066f7f2a895c821a1";
-  "19.09".ap-east-1.hvm-ebs = "ami-055b2348db2827ff1";
-  "19.09".sa-east-1.hvm-ebs = "ami-018aab68377227e06";
-
-  # 20.03.1554.94e39623a49
-  "20.03".eu-west-1.hvm-ebs = "ami-02c34db5766cc7013";
-  "20.03".eu-west-2.hvm-ebs = "ami-0e32bd8c7853883f1";
-  "20.03".eu-west-3.hvm-ebs = "ami-061edb1356c1d69fd";
-  "20.03".eu-central-1.hvm-ebs = "ami-0a1a94722dcbff94c";
-  "20.03".eu-north-1.hvm-ebs = "ami-02699abfacbb6464b";
-  "20.03".us-east-1.hvm-ebs = "ami-0c5e7760748b74e85";
-  "20.03".us-east-2.hvm-ebs = "ami-030296bb256764655";
-  "20.03".us-west-1.hvm-ebs = "ami-050be818e0266b741";
-  "20.03".us-west-2.hvm-ebs = "ami-06562f78dca68eda2";
-  "20.03".ca-central-1.hvm-ebs = "ami-02365684a173255c7";
-  "20.03".ap-southeast-1.hvm-ebs = "ami-0dbf353e168d155f7";
-  "20.03".ap-southeast-2.hvm-ebs = "ami-04c0f3a75f63daddd";
-  "20.03".ap-northeast-1.hvm-ebs = "ami-093d9cc49c191eb6c";
-  "20.03".ap-northeast-2.hvm-ebs = "ami-0087df91a7b6ebd45";
-  "20.03".ap-south-1.hvm-ebs = "ami-0a1a6b569af04af9d";
-  "20.03".ap-east-1.hvm-ebs = "ami-0d18fdd309cdefa86";
-  "20.03".sa-east-1.hvm-ebs = "ami-09859378158ae971d";
-
-  # 20.09.2016.19db3e5ea27
-  "20.09".eu-west-1.hvm-ebs = "ami-0057cb7d614329fa2";
-  "20.09".eu-west-2.hvm-ebs = "ami-0d46f16e0bb0ec8fd";
-  "20.09".eu-west-3.hvm-ebs = "ami-0e8985c3ea42f87fe";
-  "20.09".eu-central-1.hvm-ebs = "ami-0eed77c38432886d2";
-  "20.09".eu-north-1.hvm-ebs = "ami-0be5bcadd632bea14";
-  "20.09".us-east-1.hvm-ebs = "ami-0a2cce52b42daccc8";
-  "20.09".us-east-2.hvm-ebs = "ami-09378bf487b07a4d8";
-  "20.09".us-west-1.hvm-ebs = "ami-09b4337b2a9e77485";
-  "20.09".us-west-2.hvm-ebs = "ami-081d3bb5fbee0a1ac";
-  "20.09".ca-central-1.hvm-ebs = "ami-020c24c6c607e7ac7";
-  "20.09".ap-southeast-1.hvm-ebs = "ami-08f648d5db009e67d";
-  "20.09".ap-southeast-2.hvm-ebs = "ami-0be390efaccbd40f9";
-  "20.09".ap-northeast-1.hvm-ebs = "ami-0c3311601cbe8f927";
-  "20.09".ap-northeast-2.hvm-ebs = "ami-0020146701f4d56cf";
-  "20.09".ap-south-1.hvm-ebs = "ami-0117e2bd876bb40d1";
-  "20.09".ap-east-1.hvm-ebs = "ami-0c42f97e5b1fda92f";
-  "20.09".sa-east-1.hvm-ebs = "ami-021637976b094959d";
-
-  # 21.05.740.aa576357673
-  "21.05".eu-west-1.hvm-ebs = "ami-048dbc738074a3083";
-  "21.05".eu-west-2.hvm-ebs = "ami-0234cf81fec68315d";
-  "21.05".eu-west-3.hvm-ebs = "ami-020e459baf709107d";
-  "21.05".eu-central-1.hvm-ebs = "ami-0857d5d1309ab8b77";
-  "21.05".eu-north-1.hvm-ebs = "ami-05403e3ae53d3716f";
-  "21.05".us-east-1.hvm-ebs = "ami-0d3002ba40b5b9897";
-  "21.05".us-east-2.hvm-ebs = "ami-069a0ca1bde6dea52";
-  "21.05".us-west-1.hvm-ebs = "ami-0b415460a84bcf9bc";
-  "21.05".us-west-2.hvm-ebs = "ami-093cba49754abd7f8";
-  "21.05".ca-central-1.hvm-ebs = "ami-065c13e1d52d60b33";
-  "21.05".ap-southeast-1.hvm-ebs = "ami-04f570c70ff9b665e";
-  "21.05".ap-southeast-2.hvm-ebs = "ami-02a3d1df595df5ef6";
-  "21.05".ap-northeast-1.hvm-ebs = "ami-027836fddb5c56012";
-  "21.05".ap-northeast-2.hvm-ebs = "ami-0edacd41dc7700c39";
-  "21.05".ap-south-1.hvm-ebs = "ami-0b279b5bb55288059";
-  "21.05".ap-east-1.hvm-ebs = "ami-06dc98082bc55c1fc";
-  "21.05".sa-east-1.hvm-ebs = "ami-04737dd49b98936c6";
-
-  latest = self."21.05";
-}; in self
+# Compatibility shim
+let
+  lib = import ../../../lib;
+  inherit (lib) mapAttrs;
+  everything = import ./amazon-ec2-amis.nix;
+  doAllVersions = mapAttrs (versionName: doRegion);
+  doRegion = mapAttrs (regionName: systems: systems.x86_64-linux);
+in
+  doAllVersions everything
diff --git a/nixpkgs/nixos/modules/virtualisation/kubevirt.nix b/nixpkgs/nixos/modules/virtualisation/kubevirt.nix
new file mode 100644
index 000000000000..408822b6af0b
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/kubevirt.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../profiles/qemu-guest.nix
+  ];
+
+  config = {
+    fileSystems."/" = {
+      device = "/dev/disk/by-label/nixos";
+      fsType = "ext4";
+      autoResize = true;
+    };
+
+    boot.growPartition = true;
+    boot.kernelParams = [ "console=ttyS0" ];
+    boot.loader.grub.device = "/dev/vda";
+    boot.loader.timeout = 0;
+
+    services.qemuGuest.enable = true;
+    services.openssh.enable = true;
+    services.cloud-init.enable = true;
+    systemd.services."serial-getty@ttyS0".enable = true;
+
+    system.build.kubevirtImage = import ../../lib/make-disk-image.nix {
+      inherit lib config pkgs;
+      format = "qcow2";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/virtualisation/libvirtd.nix b/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
index 121e7286bc13..ab87394a30ee 100644
--- a/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixpkgs/nixos/modules/virtualisation/libvirtd.nix
@@ -254,7 +254,7 @@ in
           "allow ${e}")
         cfg.allowedBridges;
       systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ];
-      etc.ethertypes.source = "${pkgs.ebtables}/etc/ethertypes";
+      etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
     };
 
     boot.kernelModules = [ "tun" ];
diff --git a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
index e47bd59dc016..9816cc2332fb 100644
--- a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/virtualisation/lxd.nix b/nixpkgs/nixos/modules/virtualisation/lxd.nix
index 94cd22d1371c..18451b147ff5 100644
--- a/nixpkgs/nixos/modules/virtualisation/lxd.nix
+++ b/nixpkgs/nixos/modules/virtualisation/lxd.nix
@@ -34,7 +34,7 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.lxd.override { nftablesSupport = config.networking.nftables.enable; };
+        default = pkgs.lxd;
         defaultText = literalExpression "pkgs.lxd";
         description = ''
           The LXD package to use.
diff --git a/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix b/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix
index 279c96567353..0838a57f0f37 100644
--- a/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixpkgs/nixos/modules/virtualisation/nixos-containers.nix
@@ -716,9 +716,9 @@ in
               { config =
                   { config, pkgs, ... }:
                   { services.postgresql.enable = true;
-                    services.postgresql.package = pkgs.postgresql_9_6;
+                    services.postgresql.package = pkgs.postgresql_10;
 
-                    system.stateVersion = "17.03";
+                    system.stateVersion = "21.05";
                   };
               };
           }
diff --git a/nixpkgs/nixos/modules/virtualisation/oci-containers.nix b/nixpkgs/nixos/modules/virtualisation/oci-containers.nix
index 24573bba4800..5af9baff8bc1 100644
--- a/nixpkgs/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixpkgs/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 = literalExpression "pkgs.dockerTools.buildDockerImage {...};";
+          example = literalExpression "pkgs.dockerTools.buildImage {...};";
         };
 
         login = {
diff --git a/nixpkgs/nixos/modules/virtualisation/podman.nix b/nixpkgs/nixos/modules/virtualisation/podman/default.nix
index 385475c84a1a..94fd727a4b56 100644
--- a/nixpkgs/nixos/modules/virtualisation/podman.nix
+++ b/nixpkgs/nixos/modules/virtualisation/podman/default.nix
@@ -39,8 +39,8 @@ let
 in
 {
   imports = [
-    ./podman-dnsname.nix
-    ./podman-network-socket.nix
+    ./dnsname.nix
+    ./network-socket.nix
     (lib.mkRenamedOptionModule [ "virtualisation" "podman" "libpod" ] [ "virtualisation" "containers" "containersConf" ])
   ];
 
diff --git a/nixpkgs/nixos/modules/virtualisation/podman-dnsname.nix b/nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix
index beef19755079..beef19755079 100644
--- a/nixpkgs/nixos/modules/virtualisation/podman-dnsname.nix
+++ b/nixpkgs/nixos/modules/virtualisation/podman/dnsname.nix
diff --git a/nixpkgs/nixos/modules/virtualisation/podman-network-socket-ghostunnel.nix b/nixpkgs/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix
index a0e7e433164a..a0e7e433164a 100644
--- a/nixpkgs/nixos/modules/virtualisation/podman-network-socket-ghostunnel.nix
+++ b/nixpkgs/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix
diff --git a/nixpkgs/nixos/modules/virtualisation/podman-network-socket.nix b/nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix
index 1429164630b3..94d8da9d2b61 100644
--- a/nixpkgs/nixos/modules/virtualisation/podman-network-socket.nix
+++ b/nixpkgs/nixos/modules/virtualisation/podman/network-socket.nix
@@ -9,6 +9,10 @@ let
 
 in
 {
+  imports = [
+    ./network-socket-ghostunnel.nix
+  ];
+
   options.virtualisation.podman.networkSocket = {
     enable = mkOption {
       type = types.bool;
diff --git a/nixpkgs/nixos/modules/virtualisation/proxmox-image.nix b/nixpkgs/nixos/modules/virtualisation/proxmox-image.nix
new file mode 100644
index 000000000000..c537d5aed447
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix
index 37a93a29976b..39273e523e8f 100644
--- a/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix
+++ b/nixpkgs/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -14,8 +14,8 @@ in {
       };
       package = mkOption {
         type = types.package;
-        default = pkgs.qemu.ga;
-        defaultText = literalExpression "pkgs.qemu.ga";
+        default = pkgs.qemu_kvm.ga;
+        defaultText = literalExpression "pkgs.qemu_kvm.ga";
         description = "The QEMU guest agent package.";
       };
   };
diff --git a/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix b/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
index c5f71b249a6d..fa3e25afb03e 100644
--- a/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixpkgs/nixos/modules/virtualisation/qemu-vm.nix
@@ -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";
@@ -122,6 +122,12 @@ let
           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"
 
@@ -171,7 +177,7 @@ let
     '';
 
 
-  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
@@ -263,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 = {
@@ -277,7 +296,7 @@ in
     virtualisation.memorySize =
       mkOption {
         type = types.ints.positive;
-        default = 384;
+        default = 1024;
         description =
           ''
             The memory size in megabytes of the virtual machine.
@@ -299,7 +318,7 @@ in
     virtualisation.diskSize =
       mkOption {
         type = types.nullOr types.ints.positive;
-        default = 512;
+        default = 1024;
         description =
           ''
             The disk size in megabytes of the virtual machine.
@@ -310,6 +329,7 @@ in
       mkOption {
         type = types.str;
         default = "./${config.system.name}.qcow2";
+        defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
         description =
           ''
             Path to the disk image containing the root filesystem.
@@ -399,17 +419,23 @@ in
           '';
       };
 
-    virtualisation.pathsInNixDB =
+    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.
           '';
       };
 
@@ -535,7 +561,7 @@ in
       package =
         mkOption {
           type = types.package;
-          default = pkgs.qemu;
+          default = pkgs.qemu_kvm;
           example = "pkgs.qemu_test";
           description = "QEMU package to use.";
         };
@@ -608,6 +634,20 @@ in
         };
     };
 
+    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;
@@ -639,6 +679,7 @@ in
       mkOption {
         type = types.str;
         default = "./${config.system.name}-efi-vars.fd";
+        defaultText = literalExpression ''"./''${config.system.name}-efi-vars.fd"'';
         description =
           ''
             Path to nvram image containing UEFI variables.  The will be created
@@ -740,7 +781,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
@@ -759,12 +800,21 @@ in
 
     virtualisation.bootDevice = mkDefault (driveDeviceName 1);
 
-    virtualisation.pathsInNixDB = [ config.system.build.toplevel ];
+    virtualisation.additionalPaths = [ config.system.build.toplevel ];
 
     virtualisation.sharedDirectories = {
-      nix-store = { source = "/nix/store"; target = "/nix/store"; };
-      xchg      = { source = ''"$TMPDIR"/xchg''; target = "/tmp/xchg"; };
-      shared    = { source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"''; target = "/tmp/shared"; };
+      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 =
@@ -785,7 +835,8 @@ in
 
     # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
-      (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
+      [ "-device virtio-keyboard" ]
+      (mkIf pkgs.stdenv.hostPlatform.isx86 [
         "-usb" "-device usb-tablet,bus=usb-bus.0"
       ])
       (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
@@ -797,7 +848,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) [
@@ -815,6 +866,11 @@ in
         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`].
@@ -865,6 +921,13 @@ in
             options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
           };
 
+        "/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" ];
diff --git a/nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix
index 486951983d30..f702fb4e525c 100644
--- a/nixpkgs/nixos/modules/virtualisation/virtualbox-guest.nix
+++ b/nixpkgs/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/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix b/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
index 6c742ad371cd..2acf54aae2ef 100644
--- a/nixpkgs/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixpkgs/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;
   };
 
@@ -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/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix b/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
index 7b25ffc440f8..481dedf84054 100644
--- a/nixpkgs/nixos/modules/virtualisation/vmware-guest.nix
+++ b/nixpkgs/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}";
     } ];
 
@@ -34,16 +34,17 @@ in
     systemd.services.vmware =
       { description = "VMWare Guest Service";
         wantedBy = [ "multi-user.target" ];
+        after = [ "display-manager.service" ];
+        unitConfig.ConditionVirtualization = "vmware";
         serviceConfig.ExecStart = "${open-vm-tools}/bin/vmtoolsd";
       };
 
     # Mount the vmblock for drag-and-drop and copy-and-paste.
-    systemd.mounts = [
+    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" ];
+        unitConfig.ConditionVirtualization = "vmware";
         what = "${open-vm-tools}/bin/vmware-vmblock-fuse";
         where = "/run/vmblock-fuse";
         type = "fuse";
@@ -52,8 +53,8 @@ in
       }
     ];
 
-    security.wrappers.vmware-user-suid-wrapper =
-      { setuid = true;
+    security.wrappers.vmware-user-suid-wrapper = mkIf (!cfg.headless) {
+        setuid = true;
         owner = "root";
         group = "root";
         source = "${open-vm-tools}/bin/vmware-user-suid-wrapper";
diff --git a/nixpkgs/nixos/modules/virtualisation/waydroid.nix b/nixpkgs/nixos/modules/virtualisation/waydroid.nix
new file mode 100644
index 000000000000..4fc798ff39f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/virtualisation/waydroid.nix
@@ -0,0 +1,73 @@
+{ 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")
+    ];
+
+    /* NOTE: we always enable this flag even if CONFIG_PSI_DEFAULT_DISABLED is not on
+      as reading the kernel config is not always possible and on kernels where it's
+      already on it will be no-op
+    */
+    boot.kernelParams = [ "psi=1" ];
+
+    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/nixpkgs/nixos/release.nix b/nixpkgs/nixos/release.nix
index 264d82bacc8a..6b7564a9b972 100644
--- a/nixpkgs/nixos/release.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/acme.nix b/nixpkgs/nixos/tests/acme.nix
index 72b7bb8a396a..0dd7743c52b6 100644
--- a/nixpkgs/nixos/tests/acme.nix
+++ b/nixpkgs/nixos/tests/acme.nix
@@ -1,9 +1,9 @@
-let
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
   commonConfig = ./common/acme/client;
 
   dnsServerIP = nodes: nodes.dnsserver.config.networking.primaryIPAddress;
 
-  dnsScript = {pkgs, nodes}: let
+  dnsScript = nodes: let
     dnsAddress = dnsServerIP nodes;
   in pkgs.writeShellScript "dns-hook.sh" ''
     set -euo pipefail
@@ -15,30 +15,137 @@ let
     fi
   '';
 
-  documentRoot = pkgs: pkgs.runCommand "docroot" {} ''
+  dnsConfig = nodes: {
+    dnsProvider = "exec";
+    dnsPropagationCheck = false;
+    credentialsFile = pkgs.writeText "wildcard.env" ''
+      EXEC_PATH=${dnsScript nodes}
+      EXEC_POLLING_INTERVAL=1
+      EXEC_PROPAGATION_TIMEOUT=1
+      EXEC_SEQUENCE_INTERVAL=1
+    '';
+  };
+
+  documentRoot = pkgs.runCommand "docroot" {} ''
     mkdir -p "$out"
     echo hello world > "$out/index.html"
   '';
 
-  vhostBase = pkgs: {
+  vhostBase = {
     forceSSL = true;
-    locations."/".root = documentRoot pkgs;
+    locations."/".root = documentRoot;
+  };
+
+  vhostBaseHttpd = {
+    forceSSL = true;
+    inherit documentRoot;
+  };
+
+  # Base specialisation config for testing general ACME features
+  webserverBasicConfig = {
+    services.nginx.enable = true;
+    services.nginx.virtualHosts."a.example.test" = vhostBase // {
+      enableACME = true;
+    };
   };
 
-in import ./make-test-python.nix ({ lib, ... }: {
+  # Generate specialisations for testing a web server
+  mkServerConfigs = { server, group, vhostBaseData, extraConfig ? {} }: let
+    baseConfig = { nodes, config, specialConfig ? {} }: lib.mkMerge [
+      {
+        security.acme = {
+          defaults = (dnsConfig nodes) // {
+            inherit group;
+          };
+          # One manual wildcard cert
+          certs."example.test" = {
+            domain = "*.example.test";
+          };
+        };
+
+        services."${server}" = {
+          enable = true;
+          virtualHosts = {
+            # Run-of-the-mill vhost using HTTP-01 validation
+            "${server}-http.example.test" = vhostBaseData // {
+              serverAliases = [ "${server}-http-alias.example.test" ];
+              enableACME = true;
+            };
+
+            # Another which inherits the DNS-01 config
+            "${server}-dns.example.test" = vhostBaseData // {
+              serverAliases = [ "${server}-dns-alias.example.test" ];
+              enableACME = true;
+              # Set acmeRoot to null instead of using the default of "/var/lib/acme/acme-challenge"
+              # webroot + dnsProvider are mutually exclusive.
+              acmeRoot = null;
+            };
+
+            # One using the wildcard certificate
+            "${server}-wildcard.example.test" = vhostBaseData // {
+              serverAliases = [ "${server}-wildcard-alias.example.test" ];
+              useACMEHost = "example.test";
+            };
+          };
+        };
+
+        # Used to determine if service reload was triggered
+        systemd.targets."test-renew-${server}" = {
+          wants = [ "acme-${server}-http.example.test.service" ];
+          after = [ "acme-${server}-http.example.test.service" "${server}-config-reload.service" ];
+        };
+      }
+      specialConfig
+      extraConfig
+    ];
+  in {
+    "${server}".configuration = { nodes, config, ... }: baseConfig {
+      inherit nodes config;
+    };
+
+    # Test that server reloads when an alias is removed (and subsequently test removal works in acme)
+    "${server}-remove-alias".configuration = { nodes, config, ... }: baseConfig {
+      inherit nodes config;
+      specialConfig = {
+        # Remove an alias, but create a standalone vhost in its place for testing.
+        # This configuration results in certificate errors as useACMEHost does not imply
+        # append extraDomains, and thus we can validate the SAN is removed.
+        services."${server}" = {
+          virtualHosts."${server}-http.example.test".serverAliases = lib.mkForce [];
+          virtualHosts."${server}-http-alias.example.test" = vhostBaseData // {
+            useACMEHost = "${server}-http.example.test";
+          };
+        };
+      };
+    };
+
+    # Test that the server reloads when only the acme configuration is changed.
+    "${server}-change-acme-conf".configuration = { nodes, config, ... }: baseConfig {
+      inherit nodes config;
+      specialConfig = {
+        security.acme.certs."${server}-http.example.test" = {
+          keyType = "ec384";
+          # Also test that postRun is exec'd as root
+          postRun = "id | grep root";
+        };
+      };
+    };
+  };
+
+in {
   name = "acme";
   meta.maintainers = lib.teams.acme.members;
 
   nodes = {
     # The fake ACME server which will respond to client requests
-    acme = { nodes, lib, ... }: {
+    acme = { nodes, ... }: {
       imports = [ ./common/acme/server ];
       networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
     };
 
     # A fake DNS server which can be configured with records as desired
     # Used to test DNS-01 challenge
-    dnsserver = { nodes, pkgs, ... }: {
+    dnsserver = { nodes, ... }: {
       networking.firewall.allowedTCPPorts = [ 8055 53 ];
       networking.firewall.allowedUDPPorts = [ 53 ];
       systemd.services.pebble-challtestsrv = {
@@ -54,7 +161,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
     };
 
     # A web server which will be the node requesting certs
-    webserver = { pkgs, nodes, lib, config, ... }: {
+    webserver = { nodes, config, ... }: {
       imports = [ commonConfig ];
       networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
       networking.firewall.allowedTCPPorts = [ 80 443 ];
@@ -63,130 +170,142 @@ in import ./make-test-python.nix ({ lib, ... }: {
       environment.systemPackages = [ pkgs.openssl ];
 
       # Set log level to info so that we can see when the service is reloaded
-      services.nginx.enable = true;
       services.nginx.logError = "stderr info";
 
-      # First tests configure a basic cert and run a bunch of openssl checks
-      services.nginx.virtualHosts."a.example.test" = (vhostBase pkgs) // {
-        enableACME = true;
-      };
-
-      # Used to determine if service reload was triggered
-      systemd.targets.test-renew-nginx = {
-        wants = [ "acme-a.example.test.service" ];
-        after = [ "acme-a.example.test.service" "nginx-config-reload.service" ];
-      };
-
-      # Test that account creation is collated into one service
-      specialisation.account-creation.configuration = { nodes, pkgs, lib, ... }: let
-        email = "newhostmaster@example.test";
-        caDomain = nodes.acme.config.test-support.acme.caDomain;
-        # Exit 99 to make it easier to track if this is the reason a renew failed
-        testScript = ''
-          test -e accounts/${caDomain}/${email}/account.json || exit 99
-        '';
-      in {
-        security.acme.email = lib.mkForce email;
-        systemd.services."b.example.test".preStart = testScript;
-        systemd.services."c.example.test".preStart = testScript;
-
-        services.nginx.virtualHosts."b.example.test" = (vhostBase pkgs) // {
-          enableACME = true;
-        };
-        services.nginx.virtualHosts."c.example.test" = (vhostBase pkgs) // {
-          enableACME = true;
-        };
-      };
-
-      # Cert config changes will not cause the nginx configuration to change.
-      # This tests that the reload service is correctly triggered.
-      # It also tests that postRun is exec'd as root
-      specialisation.cert-change.configuration = { pkgs, ... }: {
-        security.acme.certs."a.example.test".keyType = "ec384";
-        security.acme.certs."a.example.test".postRun = ''
-          set -euo pipefail
-          touch /home/test
-          chown root:root /home/test
-          echo testing > /home/test
-        '';
-      };
-
-      # Now adding an alias to ensure that the certs are updated
-      specialisation.nginx-aliases.configuration = { pkgs, ... }: {
-        services.nginx.virtualHosts."a.example.test" = {
-          serverAliases = [ "b.example.test" ];
-        };
-      };
-
-      # Test OCSP Stapling
-      specialisation.ocsp-stapling.configuration = { pkgs, ... }: {
-        security.acme.certs."a.example.test" = {
-          ocspMustStaple = true;
-        };
-        services.nginx.virtualHosts."a.example.com" = {
-          extraConfig = ''
-            ssl_stapling on;
-            ssl_stapling_verify on;
+      specialisation = {
+        # First derivation used to test general ACME features
+        general.configuration = { ... }: let
+          caDomain = nodes.acme.config.test-support.acme.caDomain;
+          email = config.security.acme.defaults.email;
+          # Exit 99 to make it easier to track if this is the reason a renew failed
+          accountCreateTester = ''
+            test -e accounts/${caDomain}/${email}/account.json || exit 99
           '';
-        };
-      };
-
-      # Test using Apache HTTPD
-      specialisation.httpd-aliases.configuration = { pkgs, config, lib, ... }: {
-        services.nginx.enable = lib.mkForce false;
-        services.httpd.enable = true;
-        services.httpd.adminAddr = config.security.acme.email;
-        services.httpd.virtualHosts."c.example.test" = {
-          serverAliases = [ "d.example.test" ];
-          forceSSL = true;
-          enableACME = true;
-          documentRoot = documentRoot pkgs;
-        };
-
-        # Used to determine if service reload was triggered
-        systemd.targets.test-renew-httpd = {
-          wants = [ "acme-c.example.test.service" ];
-          after = [ "acme-c.example.test.service" "httpd-config-reload.service" ];
-        };
-      };
-
-      # Validation via DNS-01 challenge
-      specialisation.dns-01.configuration = { pkgs, config, nodes, ... }: {
-        security.acme.certs."example.test" = {
-          domain = "*.example.test";
-          group = config.services.nginx.group;
-          dnsProvider = "exec";
-          dnsPropagationCheck = false;
-          credentialsFile = pkgs.writeText "wildcard.env" ''
-            EXEC_PATH=${dnsScript { inherit pkgs nodes; }}
-          '';
-        };
-
-        services.nginx.virtualHosts."dns.example.test" = (vhostBase pkgs) // {
-          useACMEHost = "example.test";
-        };
-      };
-
-      # Validate service relationships by adding a slow start service to nginx' wants.
-      # Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
-      specialisation.slow-startup.configuration = { pkgs, config, nodes, lib, ... }: {
-        systemd.services.my-slow-service = {
-          wantedBy = [ "multi-user.target" "nginx.service" ];
-          before = [ "nginx.service" ];
-          preStart = "sleep 5";
-          script = "${pkgs.python3}/bin/python -m http.server";
+        in lib.mkMerge [
+          webserverBasicConfig
+          {
+            # Used to test that account creation is collated into one service.
+            # These should not run until after acme-finished-a.example.test.target
+            systemd.services."b.example.test".preStart = accountCreateTester;
+            systemd.services."c.example.test".preStart = accountCreateTester;
+
+            services.nginx.virtualHosts."b.example.test" = vhostBase // {
+              enableACME = true;
+            };
+            services.nginx.virtualHosts."c.example.test" = vhostBase // {
+              enableACME = true;
+            };
+          }
+        ];
+
+        # Test OCSP Stapling
+        ocsp-stapling.configuration = { ... }: lib.mkMerge [
+          webserverBasicConfig
+          {
+            security.acme.certs."a.example.test".ocspMustStaple = true;
+            services.nginx.virtualHosts."a.example.test" = {
+              extraConfig = ''
+                ssl_stapling on;
+                ssl_stapling_verify on;
+              '';
+            };
+          }
+        ];
+
+        # Validate service relationships by adding a slow start service to nginx' wants.
+        # Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
+        slow-startup.configuration = { ... }: lib.mkMerge [
+          webserverBasicConfig
+          {
+            systemd.services.my-slow-service = {
+              wantedBy = [ "multi-user.target" "nginx.service" ];
+              before = [ "nginx.service" ];
+              preStart = "sleep 5";
+              script = "${pkgs.python3}/bin/python -m http.server";
+            };
+
+            services.nginx.virtualHosts."slow.example.test" = {
+              forceSSL = true;
+              enableACME = true;
+              locations."/".proxyPass = "http://localhost:8000";
+            };
+          }
+        ];
+
+        # Test lego internal server (listenHTTP option)
+        # Also tests useRoot option
+        lego-server.configuration = { ... }: {
+          security.acme.useRoot = true;
+          security.acme.certs."lego.example.test" = {
+            listenHTTP = ":80";
+            group = "nginx";
+          };
+          services.nginx.enable = true;
+          services.nginx.virtualHosts."lego.example.test" = {
+            useACMEHost = "lego.example.test";
+            onlySSL = true;
+          };
         };
 
-        services.nginx.virtualHosts."slow.example.com" = {
-          forceSSL = true;
-          enableACME = true;
-          locations."/".proxyPass = "http://localhost:8000";
+      # Test compatiblity with Caddy
+      # It only supports useACMEHost, hence not using mkServerConfigs
+      } // (let
+        baseCaddyConfig = { nodes, config, ... }: {
+          security.acme = {
+            defaults = (dnsConfig nodes) // {
+              group = config.services.caddy.group;
+            };
+            # One manual wildcard cert
+            certs."example.test" = {
+              domain = "*.example.test";
+            };
+          };
+
+          services.caddy = {
+            enable = true;
+            virtualHosts."a.exmaple.test" = {
+              useACMEHost = "example.test";
+              extraConfig = ''
+                root * ${documentRoot}
+              '';
+            };
+          };
         };
-      };
+      in {
+        caddy.configuration = baseCaddyConfig;
+
+        # Test that the server reloads when only the acme configuration is changed.
+        "caddy-change-acme-conf".configuration = { nodes, config, ... }: lib.mkMerge [
+          (baseCaddyConfig {
+            inherit nodes config;
+          })
+          {
+            security.acme.certs."example.test" = {
+              keyType = "ec384";
+            };
+          }
+        ];
+
+      # Test compatibility with Nginx
+      }) // (mkServerConfigs {
+          server = "nginx";
+          group = "nginx";
+          vhostBaseData = vhostBase;
+        })
+
+      # Test compatibility with Apache HTTPD
+        // (mkServerConfigs {
+          server = "httpd";
+          group = "wwwrun";
+          vhostBaseData = vhostBaseHttpd;
+          extraConfig = {
+            services.httpd.adminAddr = config.security.acme.defaults.email;
+          };
+        });
     };
 
     # The client will be used to curl the webserver to validate configuration
-    client = {nodes, lib, pkgs, ...}: {
+    client = { nodes, ... }: {
       imports = [ commonConfig ];
       networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
 
@@ -195,7 +314,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
     };
   };
 
-  testScript = {nodes, ...}:
+  testScript = { nodes, ... }:
     let
       caDomain = nodes.acme.config.test-support.acme.caDomain;
       newServerSystem = nodes.webserver.config.system.build.toplevel;
@@ -204,23 +323,26 @@ in import ./make-test-python.nix ({ lib, ... }: {
     # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true,
     # this is because a oneshot goes from inactive => activating => inactive, and never
     # reaches the active state. Targets do not have this issue.
-
     ''
       import time
 
 
-      has_switched = False
+      def switch_to(node, name):
+          # On first switch, this will create a symlink to the current system so that we can
+          # quickly switch between derivations
+          root_specs = "/tmp/specialisation"
+          node.execute(
+            f"test -e {root_specs}"
+            f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}"
+          )
 
+          switcher_path = f"/run/current-system/specialisation/{name}/bin/switch-to-configuration"
+          rc, _ = node.execute(f"test -e '{switcher_path}'")
+          if rc > 0:
+              switcher_path = f"/tmp/specialisation/{name}/bin/switch-to-configuration"
 
-      def switch_to(node, name):
-          global has_switched
-          if has_switched:
-              node.succeed(
-                  "${switchToNewServer}"
-              )
-          has_switched = True
           node.succeed(
-              f"/run/current-system/specialisation/{name}/bin/switch-to-configuration test"
+              f"{switcher_path} test"
           )
 
 
@@ -310,8 +432,7 @@ in import ./make-test-python.nix ({ lib, ... }: {
               return download_ca_certs(node, retries - 1)
 
 
-      client.start()
-      dnsserver.start()
+      start_all()
 
       dnsserver.wait_for_unit("pebble-challtestsrv.service")
       client.wait_for_unit("default.target")
@@ -320,19 +441,30 @@ in import ./make-test-python.nix ({ lib, ... }: {
           'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.config.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a'
       )
 
-      acme.start()
-      webserver.start()
-
       acme.wait_for_unit("network-online.target")
       acme.wait_for_unit("pebble.service")
 
       download_ca_certs(client)
 
-      with subtest("Can request certificate with HTTPS-01 challenge"):
+      # Perform general tests first
+      switch_to(webserver, "general")
+
+      with subtest("Can request certificate with HTTP-01 challenge"):
           webserver.wait_for_unit("acme-finished-a.example.test.target")
+          check_fullchain(webserver, "a.example.test")
+          check_issuer(webserver, "a.example.test", "pebble")
+          webserver.wait_for_unit("nginx.service")
+          check_connection(client, "a.example.test")
+
+      with subtest("Runs 1 cert for account creation before others"):
+          webserver.wait_for_unit("acme-finished-b.example.test.target")
+          webserver.wait_for_unit("acme-finished-c.example.test.target")
+          check_connection(client, "b.example.test")
+          check_connection(client, "c.example.test")
 
       with subtest("Certificates and accounts have safe + valid permissions"):
-          group = "${nodes.webserver.config.security.acme.certs."a.example.test".group}"
+          # Nginx will set the group appropriately when enableACME is used
+          group = "nginx"
           webserver.succeed(
               f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
           )
@@ -346,12 +478,6 @@ in import ./make-test-python.nix ({ lib, ... }: {
               f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c '%a %U %G' {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0"
           )
 
-      with subtest("Certs are accepted by web server"):
-          webserver.succeed("systemctl start nginx.service")
-          check_fullchain(webserver, "a.example.test")
-          check_issuer(webserver, "a.example.test", "pebble")
-          check_connection(client, "a.example.test")
-
       # Selfsigned certs tests happen late so we aren't fighting the system init triggering cert renewal
       with subtest("Can generate valid selfsigned certs"):
           webserver.succeed("systemctl clean acme-a.example.test.service --what=state")
@@ -365,77 +491,107 @@ in import ./make-test-python.nix ({ lib, ... }: {
           # Will succeed if nginx can load the certs
           webserver.succeed("systemctl start nginx-config-reload.service")
 
-      with subtest("Can reload nginx when timer triggers renewal"):
-          webserver.succeed("systemctl start test-renew-nginx.target")
-          check_issuer(webserver, "a.example.test", "pebble")
-          check_connection(client, "a.example.test")
-
-      with subtest("Runs 1 cert for account creation before others"):
-          switch_to(webserver, "account-creation")
-          webserver.wait_for_unit("acme-finished-a.example.test.target")
-          check_connection(client, "a.example.test")
-          webserver.wait_for_unit("acme-finished-b.example.test.target")
-          webserver.wait_for_unit("acme-finished-c.example.test.target")
-          check_connection(client, "b.example.test")
-          check_connection(client, "c.example.test")
-
-      with subtest("Can reload web server when cert configuration changes"):
-          switch_to(webserver, "cert-change")
-          webserver.wait_for_unit("acme-finished-a.example.test.target")
-          check_connection_key_bits(client, "a.example.test", "384")
-          webserver.succeed("grep testing /home/test")
-          # Clean to remove the testing file (and anything else messy we did)
-          webserver.succeed("systemctl clean acme-a.example.test.service --what=state")
-
       with subtest("Correctly implements OCSP stapling"):
           switch_to(webserver, "ocsp-stapling")
           webserver.wait_for_unit("acme-finished-a.example.test.target")
           check_stapling(client, "a.example.test")
 
-      with subtest("Can request certificate with HTTPS-01 when nginx startup is delayed"):
+      with subtest("Can request certificate with HTTP-01 using lego's internal web server"):
+          switch_to(webserver, "lego-server")
+          webserver.wait_for_unit("acme-finished-lego.example.test.target")
+          webserver.wait_for_unit("nginx.service")
+          webserver.succeed("echo HENLO && systemctl cat nginx.service")
+          webserver.succeed("test \"$(stat -c '%U' /var/lib/acme/* | uniq)\" = \"root\"")
+          check_connection(client, "a.example.test")
+          check_connection(client, "lego.example.test")
+
+      with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"):
+          webserver.execute("systemctl stop nginx")
           switch_to(webserver, "slow-startup")
-          webserver.wait_for_unit("acme-finished-slow.example.com.target")
-          check_issuer(webserver, "slow.example.com", "pebble")
-          check_connection(client, "slow.example.com")
+          webserver.wait_for_unit("acme-finished-slow.example.test.target")
+          check_issuer(webserver, "slow.example.test", "pebble")
+          webserver.wait_for_unit("nginx.service")
+          check_connection(client, "slow.example.test")
 
-      with subtest("Can request certificate for vhost + aliases (nginx)"):
-          # Check the key hash before and after adding an alias. It should not change.
-          # The previous test reverts the ed384 change
-          webserver.wait_for_unit("acme-finished-a.example.test.target")
-          switch_to(webserver, "nginx-aliases")
-          webserver.wait_for_unit("acme-finished-a.example.test.target")
-          check_issuer(webserver, "a.example.test", "pebble")
+      with subtest("Works with caddy"):
+          switch_to(webserver, "caddy")
+          webserver.wait_for_unit("acme-finished-example.test.target")
+          webserver.wait_for_unit("caddy.service")
+          # FIXME reloading caddy is not sufficient to load new certs.
+          # Restart it manually until this is fixed.
+          webserver.succeed("systemctl restart caddy.service")
           check_connection(client, "a.example.test")
-          check_connection(client, "b.example.test")
 
-      with subtest("Can request certificates for vhost + aliases (apache-httpd)"):
-          try:
-              switch_to(webserver, "httpd-aliases")
-              webserver.wait_for_unit("acme-finished-c.example.test.target")
-          except Exception as err:
-              _, output = webserver.execute(
-                  "cat /var/log/httpd/*.log && ls -al /var/lib/acme/acme-challenge"
-              )
-              print(output)
-              raise err
-          check_issuer(webserver, "c.example.test", "pebble")
-          check_connection(client, "c.example.test")
-          check_connection(client, "d.example.test")
-
-      with subtest("Can reload httpd when timer triggers renewal"):
-          # Switch to selfsigned first
-          webserver.succeed("systemctl clean acme-c.example.test.service --what=state")
-          webserver.succeed("systemctl start acme-selfsigned-c.example.test.service")
-          check_issuer(webserver, "c.example.test", "minica")
-          webserver.succeed("systemctl start httpd-config-reload.service")
-          webserver.succeed("systemctl start test-renew-httpd.target")
-          check_issuer(webserver, "c.example.test", "pebble")
-          check_connection(client, "c.example.test")
-
-      with subtest("Can request wildcard certificates using DNS-01 challenge"):
-          switch_to(webserver, "dns-01")
+      with subtest("security.acme changes reflect on caddy"):
+          switch_to(webserver, "caddy-change-acme-conf")
           webserver.wait_for_unit("acme-finished-example.test.target")
-          check_issuer(webserver, "example.test", "pebble")
-          check_connection(client, "dns.example.test")
+          webserver.wait_for_unit("caddy.service")
+          # FIXME reloading caddy is not sufficient to load new certs.
+          # Restart it manually until this is fixed.
+          webserver.succeed("systemctl restart caddy.service")
+          check_connection_key_bits(client, "a.example.test", "384")
+
+      domains = ["http", "dns", "wildcard"]
+      for server, logsrc in [
+          ("nginx", "journalctl -n 30 -u nginx.service"),
+          ("httpd", "tail -n 30 /var/log/httpd/*.log"),
+      ]:
+          wait_for_server = lambda: webserver.wait_for_unit(f"{server}.service")
+          with subtest(f"Works with {server}"):
+              try:
+                  switch_to(webserver, server)
+                  # Skip wildcard domain for this check ([:-1])
+                  for domain in domains[:-1]:
+                      webserver.wait_for_unit(
+                          f"acme-finished-{server}-{domain}.example.test.target"
+                      )
+              except Exception as err:
+                  _, output = webserver.execute(
+                      f"{logsrc} && ls -al /var/lib/acme/acme-challenge"
+                  )
+                  print(output)
+                  raise err
+
+              wait_for_server()
+
+              for domain in domains[:-1]:
+                  check_issuer(webserver, f"{server}-{domain}.example.test", "pebble")
+              for domain in domains:
+                  check_connection(client, f"{server}-{domain}.example.test")
+                  check_connection(client, f"{server}-{domain}-alias.example.test")
+
+          test_domain = f"{server}-{domains[0]}.example.test"
+
+          with subtest(f"Can reload {server} when timer triggers renewal"):
+              # Switch to selfsigned first
+              webserver.succeed(f"systemctl clean acme-{test_domain}.service --what=state")
+              webserver.succeed(f"systemctl start acme-selfsigned-{test_domain}.service")
+              check_issuer(webserver, test_domain, "minica")
+              webserver.succeed(f"systemctl start {server}-config-reload.service")
+              webserver.succeed(f"systemctl start test-renew-{server}.target")
+              check_issuer(webserver, test_domain, "pebble")
+              check_connection(client, test_domain)
+
+          with subtest("Can remove an alias from a domain + cert is updated"):
+              test_alias = f"{server}-{domains[0]}-alias.example.test"
+              switch_to(webserver, f"{server}-remove-alias")
+              webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
+              wait_for_server()
+              check_connection(client, test_domain)
+              rc, _ = client.execute(
+                  f"openssl s_client -CAfile /tmp/ca.crt -connect {test_alias}:443"
+                  " </dev/null 2>/dev/null | openssl x509 -noout -text"
+                  f" | grep DNS: | grep {test_alias}"
+              )
+              assert rc > 0, "Removed extraDomainName was not removed from the cert"
+
+          with subtest("security.acme changes reflect on web server"):
+              # Switch back to normal server config first, reset everything.
+              switch_to(webserver, server)
+              wait_for_server()
+              switch_to(webserver, f"{server}-change-acme-conf")
+              webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
+              wait_for_server()
+              check_connection_key_bits(client, test_domain, "384")
     '';
 })
diff --git a/nixpkgs/nixos/tests/aesmd.nix b/nixpkgs/nixos/tests/aesmd.nix
new file mode 100644
index 000000000000..59c04fe7e96a
--- /dev/null
+++ b/nixpkgs/nixos/tests/aesmd.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "aesmd";
+  meta = {
+    maintainers = with lib.maintainers; [ veehaitch ];
+  };
+
+  machine = { lib, ... }: {
+    services.aesmd = {
+      enable = true;
+      settings = {
+        defaultQuotingType = "ecdsa_256";
+        proxyType = "direct";
+        whitelistUrl = "http://nixos.org";
+      };
+    };
+
+    # Should have access to the AESM socket
+    users.users."sgxtest" = {
+      isNormalUser = true;
+      extraGroups = [ "sgx" ];
+    };
+
+    # Should NOT have access to the AESM socket
+    users.users."nosgxtest".isNormalUser = true;
+
+    # We don't have a real SGX machine in NixOS tests
+    systemd.services.aesmd.unitConfig.AssertPathExists = lib.mkForce [ ];
+  };
+
+  testScript = ''
+    with subtest("aesmd.service starts"):
+      machine.wait_for_unit("aesmd.service")
+      status, main_pid = machine.systemctl("show --property MainPID --value aesmd.service")
+      assert status == 0, "Could not get MainPID of aesmd.service"
+      main_pid = main_pid.strip()
+
+    with subtest("aesmd.service runtime directory permissions"):
+      runtime_dir = "/run/aesmd";
+      res = machine.succeed(f"stat -c '%a %U %G' {runtime_dir}").strip()
+      assert "750 aesmd sgx" == res, f"{runtime_dir} does not have the expected permissions: {res}"
+
+    with subtest("aesm.socket available on host"):
+      socket_path = "/var/run/aesmd/aesm.socket"
+      machine.wait_until_succeeds(f"test -S {socket_path}")
+      machine.succeed(f"test 777 -eq $(stat -c '%a' {socket_path})")
+      for op in [ "-r", "-w", "-x" ]:
+        machine.succeed(f"sudo -u sgxtest test {op} {socket_path}")
+        machine.fail(f"sudo -u nosgxtest test {op} {socket_path}")
+
+    with subtest("Copies white_list_cert_to_be_verify.bin"):
+      whitelist_path = "/var/opt/aesmd/data/white_list_cert_to_be_verify.bin"
+      whitelist_perms = machine.succeed(
+        f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/stat -c '%a' {whitelist_path}"
+      ).strip()
+      assert "644" == whitelist_perms, f"white_list_cert_to_be_verify.bin has permissions {whitelist_perms}"
+
+    with subtest("Writes and binds aesm.conf in service namespace"):
+      aesmd_config = machine.succeed(f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/cat /etc/aesmd.conf")
+
+      assert aesmd_config == "whitelist url = http://nixos.org\nproxy type = direct\ndefault quoting type = ecdsa_256\n", "aesmd.conf differs"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/airsonic.nix b/nixpkgs/nixos/tests/airsonic.nix
index 59bd84877c61..d8df092c2ecf 100644
--- a/nixpkgs/nixos/tests/airsonic.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/all-tests.nix b/nixpkgs/nixos/tests/all-tests.nix
index 12b67008291e..9f3e97ceb13d 100644
--- a/nixpkgs/nixos/tests/all-tests.nix
+++ b/nixpkgs/nixos/tests/all-tests.nix
@@ -23,20 +23,18 @@ in
 {
   _3proxy = handleTest ./3proxy.nix {};
   acme = handleTest ./acme.nix {};
+  aesmd = handleTest ./aesmd.nix {};
   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 {};
@@ -47,6 +45,7 @@ in
   boot-stage1 = handleTest ./boot-stage1.nix {};
   borgbackup = handleTest ./borgbackup.nix {};
   botamusique = handleTest ./botamusique.nix {};
+  bpf = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bpf.nix {};
   btrbk = handleTest ./btrbk.nix {};
   buildbot = handleTest ./buildbot.nix {};
   buildkite-agents = handleTest ./buildkite-agents.nix {};
@@ -71,6 +70,7 @@ in
   cloud-init = handleTest ./cloud-init.nix {};
   cntr = handleTest ./cntr.nix {};
   cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
+  collectd = handleTest ./collectd.nix {};
   consul = handleTest ./consul.nix {};
   containers-bridge = handleTest ./containers-bridge.nix {};
   containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {};
@@ -105,6 +105,7 @@ in
   dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {};
   doas = handleTest ./doas.nix {};
   docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
+  docker-rootless = handleTestOn ["x86_64-linux"] ./docker-rootless.nix {};
   docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {};
   docker-registry = handleTest ./docker-registry.nix {};
   docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
@@ -112,7 +113,9 @@ 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 {};
+  drbd = handleTest ./drbd.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 {};
   ecryptfs = handleTest ./ecryptfs.nix {};
@@ -131,7 +134,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 {};
@@ -165,6 +167,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 {};
@@ -173,7 +176,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.
@@ -190,7 +195,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 {};
@@ -206,6 +211,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 {};
@@ -218,6 +224,7 @@ 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 {};
@@ -226,29 +233,35 @@ in
   kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
   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 {};
+  maddy = handleTest ./maddy.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
   magnetico = handleTest ./magnetico.nix {};
   mailcatcher = handleTest ./mailcatcher.nix {};
   mailhog = handleTest ./mailhog.nix {};
+  man = handleTest ./man.nix {};
   mariadb-galera-mariabackup = handleTest ./mysql/mariadb-galera-mariabackup.nix {};
   mariadb-galera-rsync = handleTest ./mysql/mariadb-galera-rsync.nix {};
   matomo = handleTest ./matomo.nix {};
   matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {};
   matrix-synapse = handleTest ./matrix-synapse.nix {};
+  mattermost = handleTest ./mattermost.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
   meilisearch = handleTest ./meilisearch.nix {};
   memcached = handleTest ./memcached.nix {};
@@ -259,8 +272,8 @@ 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 {};
   moodle = handleTest ./moodle.nix {};
   morty = handleTest ./morty.nix {};
@@ -309,8 +322,8 @@ in
   nginx-sso = handleTest ./nginx-sso.nix {};
   nginx-variants = handleTest ./nginx-variants.nix {};
   nitter = handleTest ./nitter.nix {};
-  nix-serve = handleTest ./nix-ssh-serve.nix {};
-  nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
+  nix-serve = handleTest ./nix-serve.nix {};
+  nix-serve-ssh = handleTest ./nix-serve-ssh.nix {};
   nixops = handleTest ./nixops/default.nix {};
   nixos-generate-config = handleTest ./nixos-generate-config.nix {};
   node-red = handleTest ./node-red.nix {};
@@ -323,6 +336,7 @@ 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 {};
@@ -336,28 +350,33 @@ in
   osrm-backend = handleTest ./osrm-backend.nix {};
   overlayfs = handleTest ./overlayfs.nix {};
   packagekit = handleTest ./packagekit.nix {};
-  pam-oath-login = handleTest ./pam-oath-login.nix {};
-  pam-u2f = handleTest ./pam-u2f.nix {};
+  pam-file-contents = handleTest ./pam/pam-file-contents.nix {};
+  pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
+  pam-u2f = handleTest ./pam/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 {};
   php74 = handleTest ./php { php = pkgs.php74; };
   php80 = handleTest ./php { php = pkgs.php80; };
+  php81 = handleTest ./php { php = pkgs.php81; };
   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 {};
   plotinus = handleTest ./plotinus.nix {};
   podgrab = handleTest ./podgrab.nix {};
-  podman = handleTestOn ["x86_64-linux"] ./podman.nix {};
-  podman-dnsname = handleTestOn ["x86_64-linux"] ./podman-dnsname.nix {};
-  podman-tls-ghostunnel = handleTestOn ["x86_64-linux"] ./podman-tls-ghostunnel.nix {};
+  podman = handleTestOn ["x86_64-linux"] ./podman/default.nix {};
+  podman-dnsname = handleTestOn ["x86_64-linux"] ./podman/dnsname.nix {};
+  podman-tls-ghostunnel = handleTestOn ["x86_64-linux"] ./podman/tls-ghostunnel.nix {};
   pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
   postfix = handleTest ./postfix.nix {};
   postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
@@ -366,6 +385,8 @@ in
   postgresql = handleTest ./postgresql.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
   powerdns = handleTest ./powerdns.nix {};
+  powerdns-admin = handleTest ./powerdns-admin.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 {};
@@ -378,11 +399,13 @@ in
   proxy = handleTest ./proxy.nix {};
   prowlarr = handleTest ./prowlarr.nix {};
   pt2-clone = handleTest ./pt2-clone.nix {};
+  pulseaudio = discoverTests (import ./pulseaudio.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 {};
@@ -393,10 +416,12 @@ in
   rss2email = handleTest ./rss2email.nix {};
   rsyslogd = handleTest ./rsyslogd.nix {};
   rxe = handleTest ./rxe.nix {};
+  sabnzbd = handleTest ./sabnzbd.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 {};
@@ -413,11 +438,13 @@ in
   solanum = handleTest ./solanum.nix {};
   solr = handleTest ./solr.nix {};
   sonarr = handleTest ./sonarr.nix {};
+  sourcehut = handleTest ./sourcehut.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 {};
+  step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
   sudo = handleTest ./sudo.nix {};
   sway = handleTest ./sway.nix {};
@@ -431,9 +458,11 @@ 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 {};
+  systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {};
   systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};
   systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
   systemd-nspawn = handleTest ./systemd-nspawn.nix {};
@@ -448,7 +477,6 @@ in
   tinydns = handleTest ./tinydns.nix {};
   tor = handleTest ./tor.nix {};
   # traefik test relies on docker-containers
-  trac = handleTest ./trac.nix {};
   traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {};
   trafficserver = handleTest ./trafficserver.nix {};
   transmission = handleTest ./transmission.nix {};
@@ -460,9 +488,9 @@ 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 {};
+  unifi = handleTest ./unifi.nix {};
   unit-php = handleTest ./web-servers/unit-php.nix {};
   upnp = handleTest ./upnp.nix {};
   usbguard = handleTest ./usbguard.nix {};
@@ -473,10 +501,11 @@ in
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
   vector = handleTest ./vector.nix {};
+  vengi-tools = handleTest ./vengi-tools.nix {};
   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 {};
@@ -493,7 +522,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/nixpkgs/nixos/tests/ammonite.nix b/nixpkgs/nixos/tests/ammonite.nix
deleted file mode 100644
index 4b674f35e3cb..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/awscli.nix b/nixpkgs/nixos/tests/awscli.nix
deleted file mode 100644
index e6741fcf1412..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/bat.nix b/nixpkgs/nixos/tests/bat.nix
deleted file mode 100644
index 0f548a590fb0..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/bittorrent.nix b/nixpkgs/nixos/tests/bittorrent.nix
index ee7a582922ce..11420cba9dce 100644
--- a/nixpkgs/nixos/tests/bittorrent.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/boot.nix b/nixpkgs/nixos/tests/boot.nix
index e8440598a822..9945a1dcd62f 100644
--- a/nixpkgs/nixos/tests/boot.nix
+++ b/nixpkgs/nixos/tests/boot.nix
@@ -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")
diff --git a/nixpkgs/nixos/tests/borgbackup.nix b/nixpkgs/nixos/tests/borgbackup.nix
index fae1d2d07138..cbb28689209b 100644
--- a/nixpkgs/nixos/tests/borgbackup.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/bpf.nix b/nixpkgs/nixos/tests/bpf.nix
new file mode 100644
index 000000000000..233c7dab1ee2
--- /dev/null
+++ b/nixpkgs/nixos/tests/bpf.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "bpf";
+  meta.maintainers = with pkgs.lib.maintainers; [ martinetd ];
+
+  machine = { pkgs, ... }: {
+    programs.bcc.enable = true;
+    environment.systemPackages = with pkgs; [ bpftrace ];
+  };
+
+  testScript = ''
+    ## bcc
+    # syscount -d 1 stops 1s after probe started so is good for that
+    print(machine.succeed("syscount -d 1"))
+
+    ## bpftrace
+    # list probes
+    machine.succeed("bpftrace -l")
+    # simple BEGIN probe (user probe on bpftrace itself)
+    print(machine.succeed("bpftrace -e 'BEGIN { print(\"ok\"); exit(); }'"))
+    # tracepoint
+    print(machine.succeed("bpftrace -e 'tracepoint:syscalls:sys_enter_* { print(probe); exit(); }'"))
+    # kprobe
+    print(machine.succeed("bpftrace -e 'kprobe:schedule { print(probe); exit() }'"))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/brscan5.nix b/nixpkgs/nixos/tests/brscan5.nix
index 715191b383cb..9aed742f6de7 100644
--- a/nixpkgs/nixos/tests/brscan5.nix
+++ b/nixpkgs/nixos/tests/brscan5.nix
@@ -23,6 +23,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     };
 
   testScript = ''
+    import re
     # sane loads libsane-brother5.so.1 successfully, and scanimage doesn't die
     strace = machine.succeed('strace scanimage -L 2>&1').split("\n")
     regexp = 'openat\(.*libsane-brother5.so.1", O_RDONLY|O_CLOEXEC\) = \d\d*$'
diff --git a/nixpkgs/nixos/tests/cage.nix b/nixpkgs/nixos/tests/cage.nix
index e6bef374d303..83bae3deeeab 100644
--- a/nixpkgs/nixos/tests/cage.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/cagebreak.nix b/nixpkgs/nixos/tests/cagebreak.nix
index 242e59f5d7ab..c6c2c632b61a 100644
--- a/nixpkgs/nixos/tests/cagebreak.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/cassandra.nix b/nixpkgs/nixos/tests/cassandra.nix
index bef3105f0a9e..a19d525c3431 100644
--- a/nixpkgs/nixos/tests/cassandra.nix
+++ b/nixpkgs/nixos/tests/cassandra.nix
@@ -41,7 +41,6 @@ let
       ];
     };
     services.cassandra = cassandraCfg ipAddress // extra;
-    virtualisation.memorySize = 1024;
   };
 in
 {
diff --git a/nixpkgs/nixos/tests/ceph-multi-node.nix b/nixpkgs/nixos/tests/ceph-multi-node.nix
index 33736e27b984..29e7c279d69a 100644
--- a/nixpkgs/nixos/tests/ceph-multi-node.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ceph-single-node-bluestore.nix b/nixpkgs/nixos/tests/ceph-single-node-bluestore.nix
index f706d4d56fcf..acaae4cf300e 100644
--- a/nixpkgs/nixos/tests/ceph-single-node-bluestore.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ceph-single-node.nix b/nixpkgs/nixos/tests/ceph-single-node.nix
index d1d56ea6708c..4fe5dc59ff8f 100644
--- a/nixpkgs/nixos/tests/ceph-single-node.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/chromium.nix b/nixpkgs/nixos/tests/chromium.nix
index ea9e19cefbc9..8965646bc5dc 100644
--- a/nixpkgs/nixos/tests/chromium.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/cifs-utils.nix b/nixpkgs/nixos/tests/cifs-utils.nix
deleted file mode 100644
index 98587b10d941..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/collectd.nix b/nixpkgs/nixos/tests/collectd.nix
new file mode 100644
index 000000000000..cb196224a231
--- /dev/null
+++ b/nixpkgs/nixos/tests/collectd.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "collectd";
+  meta = { };
+
+  machine =
+    { pkgs, ... }:
+
+    {
+      services.collectd = {
+        enable = true;
+        plugins = {
+          rrdtool = ''
+            DataDir "/var/lib/collectd/rrd"
+          '';
+          load = "";
+        };
+      };
+      environment.systemPackages = [ pkgs.rrdtool ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("collectd.service")
+    hostname = machine.succeed("hostname").strip()
+    file = f"/var/lib/collectd/rrd/{hostname}/load/load.rrd"
+    machine.wait_for_file(file);
+    machine.succeed(f"rrdinfo {file} | logger")
+    # check that this file contains a shortterm metric
+    machine.succeed(f"rrdinfo {file} | grep -F 'ds[shortterm].min = '")
+    # check that there are frequent updates
+    machine.succeed(f"cp {file} before")
+    machine.wait_until_fails(f"cmp before {file}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/common/acme/client/default.nix b/nixpkgs/nixos/tests/common/acme/client/default.nix
index 1e9885e375c7..9dbe345e7a01 100644
--- a/nixpkgs/nixos/tests/common/acme/client/default.nix
+++ b/nixpkgs/nixos/tests/common/acme/client/default.nix
@@ -5,9 +5,11 @@ let
 
 in {
   security.acme = {
-    server = "https://${caDomain}/dir";
-    email = "hostmaster@example.test";
     acceptTerms = true;
+    defaults = {
+      server = "https://${caDomain}/dir";
+      email = "hostmaster@example.test";
+    };
   };
 
   security.pki.certificateFiles = [ caCert ];
diff --git a/nixpkgs/nixos/tests/common/acme/server/default.nix b/nixpkgs/nixos/tests/common/acme/server/default.nix
index 1c3bfdf76b7e..450d49e60399 100644
--- a/nixpkgs/nixos/tests/common/acme/server/default.nix
+++ b/nixpkgs/nixos/tests/common/acme/server/default.nix
@@ -120,6 +120,11 @@ in {
         enable = true;
         description = "Pebble ACME server";
         wantedBy = [ "network.target" ];
+        environment = {
+          # We're not testing lego, we're just testing our configuration.
+          # No need to sleep.
+          PEBBLE_VA_NOSLEEP = "1";
+        };
 
         serviceConfig = {
           RuntimeDirectory = "pebble";
diff --git a/nixpkgs/nixos/tests/common/wayland-cage.nix b/nixpkgs/nixos/tests/common/wayland-cage.nix
new file mode 100644
index 000000000000..fd0700941392
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-bridge.nix b/nixpkgs/nixos/tests/containers-bridge.nix
index 12fa67c8b015..b8661fd7997c 100644
--- a/nixpkgs/nixos/tests/containers-bridge.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-ephemeral.nix b/nixpkgs/nixos/tests/containers-ephemeral.nix
index fabf0593f23a..db1631cf5b5d 100644
--- a/nixpkgs/nixos/tests/containers-ephemeral.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-extra_veth.nix b/nixpkgs/nixos/tests/containers-extra_veth.nix
index cbbb25258325..b8f3d9844064 100644
--- a/nixpkgs/nixos/tests/containers-extra_veth.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-hosts.nix b/nixpkgs/nixos/tests/containers-hosts.nix
index 1f24ed1f3c2c..3c6a15710027 100644
--- a/nixpkgs/nixos/tests/containers-hosts.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-imperative.nix b/nixpkgs/nixos/tests/containers-imperative.nix
index 1dcccfc306a3..a126a5480c03 100644
--- a/nixpkgs/nixos/tests/containers-imperative.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-ip.nix b/nixpkgs/nixos/tests/containers-ip.nix
index 5abea2dbad9f..91fdda0392a9 100644
--- a/nixpkgs/nixos/tests/containers-ip.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-macvlans.nix b/nixpkgs/nixos/tests/containers-macvlans.nix
index d0f41be8c125..a0cea8db4a1a 100644
--- a/nixpkgs/nixos/tests/containers-macvlans.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-physical_interfaces.nix b/nixpkgs/nixos/tests/containers-physical_interfaces.nix
index 57bd0eedcc33..e203f88786a3 100644
--- a/nixpkgs/nixos/tests/containers-physical_interfaces.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-portforward.nix b/nixpkgs/nixos/tests/containers-portforward.nix
index 221a6f50efd1..6cecd72f1bda 100644
--- a/nixpkgs/nixos/tests/containers-portforward.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/containers-tmpfs.nix b/nixpkgs/nixos/tests/containers-tmpfs.nix
index fd9f9a252ca8..d95178d1ff58 100644
--- a/nixpkgs/nixos/tests/containers-tmpfs.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/couchdb.nix b/nixpkgs/nixos/tests/couchdb.nix
index 049532481b15..453f5dcd66e8 100644
--- a/nixpkgs/nixos/tests/couchdb.nix
+++ b/nixpkgs/nixos/tests/couchdb.nix
@@ -56,5 +56,8 @@ with lib;
     couchdb3.succeed(
         "${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "0"}"
     )
+    couchdb3.succeed(
+        "${curlJqCheck testlogin "GET" "_node/couchdb@127.0.0.1" ".couchdb" "Welcome"}"
+    )
   '';
 })
diff --git a/nixpkgs/nixos/tests/croc.nix b/nixpkgs/nixos/tests/croc.nix
index 75a8fc991d47..5d709eb3d1cb 100644
--- a/nixpkgs/nixos/tests/croc.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/custom-ca.nix b/nixpkgs/nixos/tests/custom-ca.nix
index 05cfbbb2fdf2..a55449a397a7 100644
--- a/nixpkgs/nixos/tests/custom-ca.nix
+++ b/nixpkgs/nixos/tests/custom-ca.nix
@@ -81,8 +81,9 @@ 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;
+      virtualisation.memorySize = 600;
 
       networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
       security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
@@ -109,9 +110,7 @@ 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
         qutebrowser
         midori
@@ -153,9 +152,7 @@ in
         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": "Security Risk",
+      "firefox": "Security Risk",
       "chromium": "not private",
       "qutebrowser -T": "Certificate error",
       "midori": "Security"
@@ -166,7 +163,7 @@ in
         browser = command.split()[0]
         with subtest("Good certificate is trusted in " + browser):
             execute_as(
-                "alice", f"env P11_KIT_DEBUG=trust {command} https://good.example.com & >&2"
+                "alice", f"{command} https://good.example.com >&2 &"
             )
             wait_for_window_as("alice", browser)
             machine.wait_for_text("It works!")
@@ -174,9 +171,9 @@ in
             execute_as("alice", "xdotool key ctrl+w")  # close tab
 
         with subtest("Unknown CA is untrusted in " + browser):
-            execute_as("alice", f"{command} 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)
+            machine.succeed("pkill -f " + browser)
   '';
 })
diff --git a/nixpkgs/nixos/tests/deluge.nix b/nixpkgs/nixos/tests/deluge.nix
index f673ec2db5a7..33c57ce7c36c 100644
--- a/nixpkgs/nixos/tests/deluge.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/discourse.nix b/nixpkgs/nixos/tests/discourse.nix
index 7dd39085a007..cfac5f84a62f 100644
--- a/nixpkgs/nixos/tests/discourse.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/doas.nix b/nixpkgs/nixos/tests/doas.nix
index 5e9ce4b2c799..7f038b2bee29 100644
--- a/nixpkgs/nixos/tests/doas.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/docker-rootless.nix b/nixpkgs/nixos/tests/docker-rootless.nix
new file mode 100644
index 000000000000..e2a926eb3cb0
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-rootless.nix
@@ -0,0 +1,41 @@
+# This test runs docker and checks if simple container starts
+
+import ./make-test-python.nix ({ lib, pkgs, ...} : {
+  name = "docker-rootless";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ abbradar ];
+  };
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      virtualisation.docker.rootless.enable = true;
+
+      users.users.alice = {
+        uid = 1000;
+        isNormalUser = true;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.config.users.users.alice;
+      sudo = lib.concatStringsSep " " [
+        "XDG_RUNTIME_DIR=/run/user/${toString user.uid}"
+        "DOCKER_HOST=unix:///run/user/${toString user.uid}/docker.sock"
+        "sudo" "--preserve-env=XDG_RUNTIME_DIR,DOCKER_HOST" "-u" "alice"
+      ];
+    in ''
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_until_succeeds("${sudo} systemctl --user is-active docker.service")
+
+      machine.succeed("tar cv --files-from /dev/null | ${sudo} docker import - scratchimg")
+      machine.succeed(
+          "${sudo} docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+      )
+      machine.succeed("${sudo} docker ps | grep sleeping")
+      machine.succeed("${sudo} docker stop sleeping")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/docker-tools.nix b/nixpkgs/nixos/tests/docker-tools.nix
index 7110187e8d76..8a240ddb17f2 100644
--- a/nixpkgs/nixos/tests/docker-tools.nix
+++ b/nixpkgs/nixos/tests/docker-tools.nix
@@ -215,6 +215,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
                 f"docker run --rm  ${examples.layersOrder.imageName} cat /tmp/layer{index}"
             )
 
+    with subtest("Ensure layers unpacked in correct order before runAsRoot runs"):
+        assert "abc" in docker.succeed(
+            "docker load --input='${examples.layersUnpackOrder}'",
+            "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order"
+        )
+
     with subtest("Ensure environment variables are correctly inherited"):
         docker.succeed(
             "docker load --input='${examples.environmentVariables}'"
@@ -276,15 +282,22 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         # Ensure the image has the correct number of layers
         assert len(set_of_layers("layered-bulk-layer")) == 4
 
-    with subtest("Ensure correct behavior when no store is needed"):
+    with subtest("Ensure only minimal paths are added to the store"):
+        # TODO: make an example that has no store paths, for example by making
+        #       busybox non-self-referential.
+
         # This check tests that buildLayeredImage can build images that don't need a store.
         docker.succeed(
             "docker load --input='${pkgs.dockerTools.examples.no-store-paths}'"
         )
 
-        # This check may be loosened to allow an *empty* store rather than *no* store.
-        docker.succeed("docker run --rm no-store-paths ls /")
-        docker.fail("docker run --rm no-store-paths ls /nix/store")
+        docker.succeed("docker run --rm no-store-paths ls / >/dev/console")
+
+        # If busybox isn't self-referential, we need this line
+        #   docker.fail("docker run --rm no-store-paths ls /nix/store >/dev/console")
+        # However, it currently is self-referential, so we check that it is the
+        # only store path.
+        docker.succeed("diff <(docker run --rm no-store-paths ls /nix/store) <(basename ${pkgs.pkgsStatic.busybox}) >/dev/console")
 
     with subtest("Ensure buildLayeredImage does not change store path contents."):
         docker.succeed(
@@ -379,11 +392,21 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/jane | grep -E ^1000$'"
         )
 
+    with subtest("The image contains store paths referenced by the fakeRootCommands output"):
+        docker.succeed(
+            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} /hello/bin/layeredImageWithFakeRootCommands-hello"
+        )
+
     with subtest("exportImage produces a valid tarball"):
         docker.succeed(
             "tar -tf ${examples.exportBash} | grep '\./bin/bash' > /dev/null"
         )
 
+    with subtest("layered image fakeRootCommands with fakechroot works"):
+        docker.succeed("${examples.imageViaFakeChroot} | docker load")
+        docker.succeed("docker run --rm image-via-fake-chroot | grep -i hello")
+        docker.succeed("docker image rm image-via-fake-chroot:latest")
+
     with subtest("Ensure bare paths in contents are loaded correctly"):
         docker.succeed(
             "docker load --input='${examples.build-image-with-path}'",
diff --git a/nixpkgs/nixos/tests/domination.nix b/nixpkgs/nixos/tests/domination.nix
new file mode 100644
index 000000000000..c76d4ed8c61b
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/drbd.nix b/nixpkgs/nixos/tests/drbd.nix
new file mode 100644
index 000000000000..bede7206d706
--- /dev/null
+++ b/nixpkgs/nixos/tests/drbd.nix
@@ -0,0 +1,87 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  let
+    drbdPort = 7789;
+
+    drbdConfig =
+      { nodes, ... }:
+      {
+        virtualisation.emptyDiskImages = [ 1 ];
+        networking.firewall.allowedTCPPorts = [ drbdPort ];
+
+        services.drbd = {
+          enable = true;
+          config = ''
+            global {
+              usage-count yes;
+            }
+
+            common {
+              net {
+                protocol C;
+                ping-int 1;
+              }
+            }
+
+            resource r0 {
+              volume 0 {
+                device    /dev/drbd0;
+                disk      /dev/vdb;
+                meta-disk internal;
+              }
+
+              on drbd1 {
+                address ${nodes.drbd1.config.networking.primaryIPAddress}:${toString drbdPort};
+              }
+
+              on drbd2 {
+                address ${nodes.drbd2.config.networking.primaryIPAddress}:${toString drbdPort};
+              }
+            }
+          '';
+        };
+      };
+  in
+  {
+    name = "drbd";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ ryantm astro ];
+    };
+
+    nodes.drbd1 = drbdConfig;
+    nodes.drbd2 = drbdConfig;
+
+    testScript = { nodes }: ''
+      drbd1.start()
+      drbd2.start()
+
+      drbd1.wait_for_unit("network.target")
+      drbd2.wait_for_unit("network.target")
+
+      drbd1.succeed(
+          "drbdadm create-md r0",
+          "drbdadm up r0",
+          "drbdadm primary r0 --force",
+      )
+
+      drbd2.succeed("drbdadm create-md r0", "drbdadm up r0")
+
+      drbd1.succeed(
+          "mkfs.ext4 /dev/drbd0",
+          "mkdir -p /mnt/drbd",
+          "mount /dev/drbd0 /mnt/drbd",
+          "touch /mnt/drbd/hello",
+          "umount /mnt/drbd",
+          "drbdadm secondary r0",
+      )
+      drbd1.sleep(1)
+
+      drbd2.succeed(
+          "drbdadm primary r0",
+          "mkdir -p /mnt/drbd",
+          "mount /dev/drbd0 /mnt/drbd",
+          "ls /mnt/drbd/hello",
+      )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/elk.nix b/nixpkgs/nixos/tests/elk.nix
index 2a1a4cba2956..f42be00f23b8 100644
--- a/nixpkgs/nixos/tests/elk.nix
+++ b/nixpkgs/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 :
@@ -37,9 +40,8 @@ let
 
             services = {
 
-              journalbeat = let lt6 = builtins.compareVersions
-                                        elk.journalbeat.version "6" < 0; in {
-                enable = true;
+              journalbeat = {
+                enable = elk ? journalbeat;
                 package = elk.journalbeat;
                 extraConfig = pkgs.lib.mkOptionDefault (''
                   logging:
@@ -48,14 +50,29 @@ let
                     metrics.enabled: false
                   output.elasticsearch:
                     hosts: [ "127.0.0.1:9200" ]
-                    ${pkgs.lib.optionalString lt6 "template.enabled: false"}
-                '' + pkgs.lib.optionalString (!lt6) ''
                   journalbeat.inputs:
                   - paths: []
                     seek: cursor
                 '');
               };
 
+              filebeat = {
+                enable = elk ? filebeat;
+                package = elk.filebeat;
+                inputs.journald.id = "everything";
+
+                inputs.log = {
+                  enabled = true;
+                  paths = [
+                    "/var/lib/filebeat/test"
+                  ];
+                };
+
+                settings = {
+                  logging.level = "info";
+                };
+              };
+
               metricbeat = {
                 enable = true;
                 package = elk.metricbeat;
@@ -139,27 +156,43 @@ let
       };
 
     passthru.elkPackages = elk;
-    testScript = ''
+    testScript =
+      let
+        valueObject = lib.optionalString (lib.versionAtLeast elk.elasticsearch.version "7") ".value";
+      in ''
       import json
 
 
-      def total_hits(message):
+      def expect_hits(message):
           dictionary = {"query": {"match": {"message": message}}}
           return (
-              "curl --silent --show-error '${esUrl}/_search' "
+              "curl --silent --show-error --fail-with-body '${esUrl}/_search' "
               + "-H 'Content-Type: application/json' "
               + "-d '{}' ".format(json.dumps(dictionary))
-              + "| jq .hits.total"
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+          )
+
+
+      def expect_no_hits(message):
+          dictionary = {"query": {"match": {"message": message}}}
+          return (
+              "curl --silent --show-error --fail-with-body '${esUrl}/_search' "
+              + "-H 'Content-Type: application/json' "
+              + "-d '{}' ".format(json.dumps(dictionary))
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} == 0 end'"
           )
 
 
       def has_metricbeat():
           dictionary = {"query": {"match": {"event.dataset": {"query": "system.cpu"}}}}
           return (
-              "curl --silent --show-error '${esUrl}/_search' "
+              "curl --silent --show-error --fail-with-body '${esUrl}/_search' "
               + "-H 'Content-Type: application/json' "
               + "-d '{}' ".format(json.dumps(dictionary))
-              + "| jq '.hits.total > 0'"
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
           )
 
 
@@ -175,7 +208,8 @@ let
       # TODO: extend this test with multiple elasticsearch nodes
       #       and see if the status turns "green".
       one.wait_until_succeeds(
-          "curl --silent --show-error '${esUrl}/_cluster/health' | jq .status | grep -v red"
+          "curl --silent --show-error --fail-with-body '${esUrl}/_cluster/health'"
+          + " | jq -es 'if . == [] then null else .[] | .status != \"red\" end'"
       )
 
       with subtest("Perform some simple logstash tests"):
@@ -186,67 +220,86 @@ let
       with subtest("Kibana is healthy"):
           one.wait_for_unit("kibana.service")
           one.wait_until_succeeds(
-              "curl --silent --show-error 'http://localhost:5601/api/status' | jq .status.overall.state | grep green"
+              "curl --silent --show-error --fail-with-body 'http://localhost:5601/api/status'"
+              + " | jq -es 'if . == [] then null else .[] | .status.overall.state == \"green\" end'"
           )
 
       with subtest("Metricbeat is running"):
           one.wait_for_unit("metricbeat.service")
 
       with subtest("Metricbeat metrics arrive in elasticsearch"):
-          one.wait_until_succeeds(has_metricbeat() + " | tee /dev/console | grep 'true'")
+          one.wait_until_succeeds(has_metricbeat())
 
       with subtest("Logstash messages arive in elasticsearch"):
-          one.wait_until_succeeds(total_hits("flowers") + " | grep -v 0")
-          one.wait_until_succeeds(total_hits("dragons") + " | grep 0")
+          one.wait_until_succeeds(expect_hits("flowers"))
+          one.wait_until_succeeds(expect_no_hits("dragons"))
 
+    '' + lib.optionalString (elk ? journalbeat) ''
       with subtest(
           "A message logged to the journal is ingested by elasticsearch via journalbeat"
       ):
           one.wait_for_unit("journalbeat.service")
           one.execute("echo 'Supercalifragilisticexpialidocious' | systemd-cat")
           one.wait_until_succeeds(
-              total_hits("Supercalifragilisticexpialidocious") + " | grep -v 0"
+              expect_hits("Supercalifragilisticexpialidocious")
           )
-
+    '' + lib.optionalString (elk ? filebeat) ''
+      with subtest(
+          "A message logged to the journal is ingested by elasticsearch via filebeat"
+      ):
+          one.wait_for_unit("filebeat.service")
+          one.execute("echo 'Superdupercalifragilisticexpialidocious' | systemd-cat")
+          one.wait_until_succeeds(
+              expect_hits("Superdupercalifragilisticexpialidocious")
+          )
+          one.execute(
+              "echo 'SuperdupercalifragilisticexpialidociousIndeed' >> /var/lib/filebeat/test"
+          )
+          one.wait_until_succeeds(
+              expect_hits("SuperdupercalifragilisticexpialidociousIndeed")
+          )
+    '' + ''
       with subtest("Elasticsearch-curator works"):
           one.systemctl("stop logstash")
           one.systemctl("start elasticsearch-curator")
           one.wait_until_succeeds(
-              '! curl --silent --show-error "${esUrl}/_cat/indices" | grep logstash | grep ^'
+              '! curl --silent --show-error --fail-with-body "${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;
+  #   filebeat      = pkgs.filebeat7;
+  #   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;
+      filebeat      = pkgs.filebeat7;
       metricbeat    = pkgs.metricbeat7;
     };
+  };
 }
diff --git a/nixpkgs/nixos/tests/emacs-daemon.nix b/nixpkgs/nixos/tests/emacs-daemon.nix
index 58bcd095990a..e12da56021da 100644
--- a/nixpkgs/nixos/tests/emacs-daemon.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/enlightenment.nix b/nixpkgs/nixos/tests/enlightenment.nix
index 4623574ce92e..8506c348246d 100644
--- a/nixpkgs/nixos/tests/enlightenment.nix
+++ b/nixpkgs/nixos/tests/enlightenment.nix
@@ -19,7 +19,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;
     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/nixpkgs/nixos/tests/etesync-dav.nix b/nixpkgs/nixos/tests/etesync-dav.nix
index da5c056f5349..6a747e23f76f 100644
--- a/nixpkgs/nixos/tests/etesync-dav.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/fcitx/default.nix b/nixpkgs/nixos/tests/fcitx/default.nix
index cbeb95d33b0c..a243be8dc19b 100644
--- a/nixpkgs/nixos/tests/fcitx/default.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/fenics.nix b/nixpkgs/nixos/tests/fenics.nix
index 56f09d6a27e4..f0a8c32c7cd8 100644
--- a/nixpkgs/nixos/tests/fenics.nix
+++ b/nixpkgs/nixos/tests/fenics.nix
@@ -38,7 +38,6 @@ in
         gcc
         (python3.withPackages (ps: with ps; [ fenics ]))
       ];
-      virtualisation.memorySize = 512;
     };
   };
   testScript =
diff --git a/nixpkgs/nixos/tests/firefox.nix b/nixpkgs/nixos/tests/firefox.nix
index dcaf369b62bd..6101fc973564 100644
--- a/nixpkgs/nixos/tests/firefox.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ft2-clone.nix b/nixpkgs/nixos/tests/ft2-clone.nix
index c877054234ec..71eda43e2b24 100644
--- a/nixpkgs/nixos/tests/ft2-clone.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/gerrit.nix b/nixpkgs/nixos/tests/gerrit.nix
index b6b6486fae86..8ae9e89cf6b0 100644
--- a/nixpkgs/nixos/tests/gerrit.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ghostunnel.nix b/nixpkgs/nixos/tests/ghostunnel.nix
index a82cff8082b7..8bea64854021 100644
--- a/nixpkgs/nixos/tests/ghostunnel.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/gitlab.nix b/nixpkgs/nixos/tests/gitlab.nix
index 3e9feeb0769d..dc3b889c8e8e 100644
--- a/nixpkgs/nixos/tests/gitlab.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/gnome-xorg.nix b/nixpkgs/nixos/tests/gnome-xorg.nix
index b9ff5e682875..6264b87af4ec 100644
--- a/nixpkgs/nixos/tests/gnome-xorg.nix
+++ b/nixpkgs/nixos/tests/gnome-xorg.nix
@@ -40,7 +40,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
         };
       };
 
-      virtualisation.memorySize = 1024;
     };
 
   testScript = { nodes, ... }: let
diff --git a/nixpkgs/nixos/tests/gnome.nix b/nixpkgs/nixos/tests/gnome.nix
index 1da97f733cfd..06f387ecad67 100644
--- a/nixpkgs/nixos/tests/gnome.nix
+++ b/nixpkgs/nixos/tests/gnome.nix
@@ -45,7 +45,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
         };
       };
 
-      virtualisation.memorySize = 1024;
     };
 
   testScript = { nodes, ... }: let
diff --git a/nixpkgs/nixos/tests/graphite.nix b/nixpkgs/nixos/tests/graphite.nix
index 137be2d89c8b..496f16846ea6 100644
--- a/nixpkgs/nixos/tests/graphite.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/hadoop/hadoop.nix b/nixpkgs/nixos/tests/hadoop/hadoop.nix
new file mode 100644
index 000000000000..48737debab54
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/hadoop/hdfs.nix b/nixpkgs/nixos/tests/hadoop/hdfs.nix
index f1f98ed42eb3..b63cbf480327 100644
--- a/nixpkgs/nixos/tests/hadoop/hdfs.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/hadoop/yarn.nix b/nixpkgs/nixos/tests/hadoop/yarn.nix
index 01077245d397..09bdb35791c7 100644
--- a/nixpkgs/nixos/tests/hadoop/yarn.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/handbrake.nix b/nixpkgs/nixos/tests/handbrake.nix
index c92fb5db7d6c..d2d41b372be1 100644
--- a/nixpkgs/nixos/tests/handbrake.nix
+++ b/nixpkgs/nixos/tests/handbrake.nix
@@ -1,11 +1,15 @@
 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 = {
@@ -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/nixpkgs/nixos/tests/hibernate.nix b/nixpkgs/nixos/tests/hibernate.nix
index ae506c8542fe..4f05b99a5a11 100644
--- a/nixpkgs/nixos/tests/hibernate.nix
+++ b/nixpkgs/nixos/tests/hibernate.nix
@@ -68,7 +68,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 +78,8 @@ in makeTest {
                   "name": name,
               }
           )
+          driver.machines.append(machine)
+          return machine
 
 
       # Install NixOS
@@ -93,7 +95,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 +110,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/nixpkgs/nixos/tests/home-assistant.nix b/nixpkgs/nixos/tests/home-assistant.nix
index 699be8fd7dc6..1ab5755863f7 100644
--- a/nixpkgs/nixos/tests/home-assistant.nix
+++ b/nixpkgs/nixos/tests/home-assistant.nix
@@ -12,17 +12,23 @@ 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;
       enable = true;
+      package = (pkgs.home-assistant.override {
+        extraComponents = [ "zha" ];
+      }).overrideAttrs (oldAttrs: {
+        doInstallCheck = false;
+      });
       config = {
         homeassistant = {
           name = "Home";
@@ -86,6 +92,8 @@ in {
     with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
         hass.wait_for_open_port(80)
         hass.succeed("curl --fail http://localhost:80/description.xml")
+    with subtest("Check extra components are considered in systemd unit hardening"):
+        hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
     with subtest("Print log to ease debugging"):
         output_log = hass.succeed("cat ${configDir}/home-assistant.log")
         print("\n### home-assistant.log ###\n")
diff --git a/nixpkgs/nixos/tests/hydra/default.nix b/nixpkgs/nixos/tests/hydra/default.nix
index d92f032b8292..ef5e677953dc 100644
--- a/nixpkgs/nixos/tests/hydra/default.nix
+++ b/nixpkgs/nixos/tests/hydra/default.nix
@@ -17,7 +17,7 @@ let
   makeHydraTest = with pkgs.lib; name: package: makeTest {
     name = "hydra-${name}";
     meta = with pkgs.lib.maintainers; {
-      maintainers = [ pstn lewo ma27 ];
+      maintainers = [ lewo ma27 ];
     };
 
     machine = { pkgs, lib, ... }: {
diff --git a/nixpkgs/nixos/tests/ihatemoney.nix b/nixpkgs/nixos/tests/ihatemoney/default.nix
index 0451a4505808..78278d2e8699 100644
--- a/nixpkgs/nixos/tests/ihatemoney.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ihatemoney/rates.json b/nixpkgs/nixos/tests/ihatemoney/rates.json
new file mode 100644
index 000000000000..ebdd2651b040
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ihatemoney/server.crt b/nixpkgs/nixos/tests/ihatemoney/server.crt
new file mode 100644
index 000000000000..10e568b14b14
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/ihatemoney/server.key b/nixpkgs/nixos/tests/ihatemoney/server.key
new file mode 100644
index 000000000000..72a43577d64d
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/initrd-secrets.nix b/nixpkgs/nixos/tests/initrd-secrets.nix
index 10dd908502d5..113a9cebf788 100644
--- a/nixpkgs/nixos/tests/initrd-secrets.nix
+++ b/nixpkgs/nixos/tests/initrd-secrets.nix
@@ -13,7 +13,12 @@ let
 
     machine = { ... }: {
       virtualisation.useBootLoader = true;
-      boot.initrd.secrets."/test" = secretInStore;
+      boot.initrd.secrets = {
+        "/test" = secretInStore;
+
+        # This should *not* need to be copied in postMountCommands
+        "/run/keys/test" = secretInStore;
+      };
       boot.initrd.postMountCommands = ''
         cp /test /mnt-root/secret-from-initramfs
       '';
@@ -26,7 +31,8 @@ let
       start_all()
       machine.wait_for_unit("multi-user.target")
       machine.succeed(
-          "cmp ${secretInStore} /secret-from-initramfs"
+          "cmp ${secretInStore} /secret-from-initramfs",
+          "cmp ${secretInStore} /run/keys/test",
       )
     '';
   };
diff --git a/nixpkgs/nixos/tests/installed-tests/default.nix b/nixpkgs/nixos/tests/installed-tests/default.nix
index 6c2846a1636b..08785e5e6669 100644
--- a/nixpkgs/nixos/tests/installed-tests/default.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/installed-tests/fwupd.nix b/nixpkgs/nixos/tests/installed-tests/fwupd.nix
index a8a683a1af7b..65614e2689d8 100644
--- a/nixpkgs/nixos/tests/installed-tests/fwupd.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/installed-tests/power-profiles-daemon.nix b/nixpkgs/nixos/tests/installed-tests/power-profiles-daemon.nix
new file mode 100644
index 000000000000..43629a0155d2
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/installer.nix b/nixpkgs/nixos/tests/installer.nix
index 48f0f5934255..bc41b6efc2e7 100644
--- a/nixpkgs/nixos/tests/installer.nix
+++ b/nixpkgs/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()
 
@@ -353,8 +354,8 @@ let
       createPartitions = ''
         machine.succeed(
             "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
-            + " mkpart primary ext2 1M 50MB"  # /boot
-            + " mkpart primary linux-swap 50M 1024M"
+            + " mkpart primary ext2 1M 100MB"  # /boot
+            + " mkpart primary linux-swap 100M 1024M"
             + " mkpart primary 1024M -1s",  # LUKS
             "udevadm settle",
             "mkswap /dev/vda2 -L swap",
@@ -455,9 +456,9 @@ in {
     createPartitions = ''
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
-          + " mkpart ESP fat32 1M 50MiB"  # /boot
+          + " mkpart ESP fat32 1M 100MiB"  # /boot
           + " set 1 boot on"
-          + " mkpart primary linux-swap 50MiB 1024MiB"
+          + " mkpart primary linux-swap 100MiB 1024MiB"
           + " mkpart primary ext2 1024MiB -1MiB",  # /
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
@@ -482,8 +483,8 @@ in {
     createPartitions = ''
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
-          + " mkpart primary ext2 1M 50MB"  # /boot
-          + " mkpart primary linux-swap 50MB 1024M"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100MB 1024M"
           + " mkpart primary ext2 1024M -1s",  # /
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
@@ -502,8 +503,8 @@ in {
     createPartitions = ''
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
-          + " mkpart primary ext2 1M 50MB"  # /boot
-          + " mkpart primary linux-swap 50MB 1024M"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100MB 1024M"
           + " mkpart primary ext2 1024M -1s",  # /
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
@@ -598,8 +599,8 @@ in {
     createPartitions = ''
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
-          + " mkpart primary ext2 1M 50MB"  # /boot
-          + " mkpart primary linux-swap 50M 1024M"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100M 1024M"
           + " mkpart primary 1024M 1280M"  # LUKS with keyfile
           + " mkpart primary 1280M -1s",
           "udevadm settle",
@@ -673,8 +674,8 @@ in {
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda --"
           + " mklabel msdos"
-          + " mkpart primary ext2 1M 50MB"  # /boot
-          + " mkpart primary 50MB 512MB  "  # swap
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary 100MB 512MB  "  # swap
           + " mkpart primary 512MB 1024MB"  # Cache (typically SSD)
           + " mkpart primary 1024MB -1s ",  # Backing device (typically HDD)
           "modprobe bcache",
diff --git a/nixpkgs/nixos/tests/invidious.nix b/nixpkgs/nixos/tests/invidious.nix
new file mode 100644
index 000000000000..8b831715a441
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/jibri.nix b/nixpkgs/nixos/tests/jibri.nix
new file mode 100644
index 000000000000..3dd28e6aac1a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/jitsi-meet.nix b/nixpkgs/nixos/tests/jitsi-meet.nix
index f9a0b121a2bf..d95f7c2ea9ea 100644
--- a/nixpkgs/nixos/tests/jitsi-meet.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/kafka.nix b/nixpkgs/nixos/tests/kafka.nix
index 95711808a2c3..5def759ca24d 100644
--- a/nixpkgs/nixos/tests/kafka.nix
+++ b/nixpkgs/nixos/tests/kafka.nix
@@ -19,7 +19,6 @@ let
         };
 
         networking.firewall.allowedTCPPorts = [ 2181 ];
-        virtualisation.memorySize = 1024;
       };
       kafka = { ... }: {
         services.apache-kafka = {
diff --git a/nixpkgs/nixos/tests/keepassxc.nix b/nixpkgs/nixos/tests/keepassxc.nix
index 98902187f6ac..685a200b3187 100644
--- a/nixpkgs/nixos/tests/keepassxc.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/kernel-generic.nix b/nixpkgs/nixos/tests/kernel-generic.nix
index 192dc810d7ad..45c5c1963a0d 100644
--- a/nixpkgs/nixos/tests/kernel-generic.nix
+++ b/nixpkgs/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_14
-
-      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/nixpkgs/nixos/tests/kexec.nix b/nixpkgs/nixos/tests/kexec.nix
index ec0cd9796b0e..010f3da49846 100644
--- a/nixpkgs/nixos/tests/kexec.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/keycloak.nix b/nixpkgs/nixos/tests/keycloak.nix
index fc321b8902f1..1be3fed6acc9 100644
--- a/nixpkgs/nixos/tests/keycloak.nix
+++ b/nixpkgs/nixos/tests/keycloak.nix
@@ -17,7 +17,6 @@ let
 
       nodes = {
         keycloak = { ... }: {
-          virtualisation.memorySize = 1024;
 
           security.pki.certificateFiles = [
             certs.ca.cert
diff --git a/nixpkgs/nixos/tests/keymap.nix b/nixpkgs/nixos/tests/keymap.nix
index a18a05f90c6d..4306a9ae2cf9 100644
--- a/nixpkgs/nixos/tests/keymap.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/knot.nix b/nixpkgs/nixos/tests/knot.nix
index 22279292f77f..203fd03fac26 100644
--- a/nixpkgs/nixos/tests/knot.nix
+++ b/nixpkgs/nixos/tests/knot.nix
@@ -45,6 +45,10 @@ in {
   nodes = {
     master = { lib, ... }: {
       imports = [ common ];
+
+      # trigger sched_setaffinity syscall
+      virtualisation.cores = 2;
+
       networking.interfaces.eth1 = {
         ipv4.addresses = lib.mkForce [
           { address = "192.168.0.1"; prefixLength = 24; }
@@ -206,5 +210,7 @@ in {
 
             test(host, "RRSIG", "www.example.com", r"RR set signature is")
             test(host, "DNSKEY", "example.com", r"DNSSEC key is")
+
+    master.log(master.succeed("systemd-analyze security knot.service | grep -v '✓'"))
   '';
 })
diff --git a/nixpkgs/nixos/tests/kubernetes/base.nix b/nixpkgs/nixos/tests/kubernetes/base.nix
index 1f23ca55fb23..e1736f6fe172 100644
--- a/nixpkgs/nixos/tests/kubernetes/base.nix
+++ b/nixpkgs/nixos/tests/kubernetes/base.nix
@@ -51,7 +51,6 @@ let
               environment.systemPackages = [ kubectl ];
               services.flannel.iface = "eth1";
               services.kubernetes = {
-                addons.dashboard.enable = true;
                 proxy.hostname = "${masterName}.${domain}";
 
                 easyCerts = true;
diff --git a/nixpkgs/nixos/tests/kubernetes/default.nix b/nixpkgs/nixos/tests/kubernetes/default.nix
index 90b73c68a76d..60ba482758fb 100644
--- a/nixpkgs/nixos/tests/kubernetes/default.nix
+++ b/nixpkgs/nixos/tests/kubernetes/default.nix
@@ -1,5 +1,5 @@
 { system ? builtins.currentSystem
-, pkgs ? import <nixpkgs> { inherit system; }
+, pkgs ? import ../../.. { inherit system; }
 }:
 let
   dns = import ./dns.nix { inherit system pkgs; };
diff --git a/nixpkgs/nixos/tests/kubernetes/dns.nix b/nixpkgs/nixos/tests/kubernetes/dns.nix
index b6cd811c5aef..3fd1dd31f746 100644
--- a/nixpkgs/nixos/tests/kubernetes/dns.nix
+++ b/nixpkgs/nixos/tests/kubernetes/dns.nix
@@ -1,4 +1,4 @@
-{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+{ system ? builtins.currentSystem, pkgs ? import ../../.. { inherit system; } }:
 with import ./base.nix { inherit system; };
 let
   domain = "my.zyx";
@@ -100,7 +100,7 @@ let
       machine1.succeed("host redis.default.svc.cluster.local")
 
       # check dns inside the container
-      machine1.succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local")
+      machine1.succeed("kubectl exec probe -- /bin/host redis.default.svc.cluster.local")
     '';
   };
 
@@ -142,7 +142,7 @@ let
       machine2.succeed("host redis.default.svc.cluster.local")
 
       # check dns inside the container
-      machine1.succeed("kubectl exec -ti probe -- /bin/host redis.default.svc.cluster.local")
+      machine1.succeed("kubectl exec probe -- /bin/host redis.default.svc.cluster.local")
     '';
   };
 in {
diff --git a/nixpkgs/nixos/tests/kubernetes/e2e.nix b/nixpkgs/nixos/tests/kubernetes/e2e.nix
index 175d8413045e..fb29d9cc6953 100644
--- a/nixpkgs/nixos/tests/kubernetes/e2e.nix
+++ b/nixpkgs/nixos/tests/kubernetes/e2e.nix
@@ -1,4 +1,4 @@
-{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+{ system ? builtins.currentSystem, pkgs ? import ../../.. { inherit system; } }:
 with import ./base.nix { inherit system; };
 let
   domain = "my.zyx";
diff --git a/nixpkgs/nixos/tests/kubernetes/rbac.nix b/nixpkgs/nixos/tests/kubernetes/rbac.nix
index 3fc8ed0fbe38..ca73562256e4 100644
--- a/nixpkgs/nixos/tests/kubernetes/rbac.nix
+++ b/nixpkgs/nixos/tests/kubernetes/rbac.nix
@@ -1,4 +1,4 @@
-{ system ? builtins.currentSystem, pkgs ? import <nixpkgs> { inherit system; } }:
+{ system ? builtins.currentSystem, pkgs ? import ../../.. { inherit system; } }:
 with import ./base.nix { inherit system; };
 let
 
@@ -115,9 +115,9 @@ let
 
       machine1.wait_until_succeeds("kubectl get pod kubectl | grep Running")
 
-      machine1.wait_until_succeeds("kubectl exec -ti kubectl -- kubectl get pods")
-      machine1.fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json")
-      machine1.fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl")
+      machine1.wait_until_succeeds("kubectl exec kubectl -- kubectl get pods")
+      machine1.fail("kubectl exec kubectl -- kubectl create -f /kubectl-pod-2.json")
+      machine1.fail("kubectl exec kubectl -- kubectl delete pods -l name=kubectl")
     '';
   };
 
@@ -152,9 +152,9 @@ let
 
       machine1.wait_until_succeeds("kubectl get pod kubectl | grep Running")
 
-      machine1.wait_until_succeeds("kubectl exec -ti kubectl -- kubectl get pods")
-      machine1.fail("kubectl exec -ti kubectl -- kubectl create -f /kubectl-pod-2.json")
-      machine1.fail("kubectl exec -ti kubectl -- kubectl delete pods -l name=kubectl")
+      machine1.wait_until_succeeds("kubectl exec kubectl -- kubectl get pods")
+      machine1.fail("kubectl exec kubectl -- kubectl create -f /kubectl-pod-2.json")
+      machine1.fail("kubectl exec kubectl -- kubectl delete pods -l name=kubectl")
     '';
   };
 
diff --git a/nixpkgs/nixos/tests/libinput.nix b/nixpkgs/nixos/tests/libinput.nix
new file mode 100644
index 000000000000..2f84aaadcd0b
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/libresprite.nix b/nixpkgs/nixos/tests/libresprite.nix
new file mode 100644
index 000000000000..1a6210e3671a
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/libreswan.nix b/nixpkgs/nixos/tests/libreswan.nix
index 17ae60af8eed..56ab908aed9a 100644
--- a/nixpkgs/nixos/tests/libreswan.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/lorri/default.nix b/nixpkgs/nixos/tests/lorri/default.nix
index c33c7503993d..147ae999fdb1 100644
--- a/nixpkgs/nixos/tests/lorri/default.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/lsd.nix b/nixpkgs/nixos/tests/lsd.nix
deleted file mode 100644
index c643f2f0b7b7..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/lxd-image-server.nix b/nixpkgs/nixos/tests/lxd-image-server.nix
new file mode 100644
index 000000000000..9f060fed38d8
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/lxd-image.nix b/nixpkgs/nixos/tests/lxd-image.nix
new file mode 100644
index 000000000000..096b9d9aba90
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/lxd.nix b/nixpkgs/nixos/tests/lxd.nix
index 889ca9598e3f..1a3b84a85cf6 100644
--- a/nixpkgs/nixos/tests/lxd.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/maddy.nix b/nixpkgs/nixos/tests/maddy.nix
new file mode 100644
index 000000000000..581748c1fa59
--- /dev/null
+++ b/nixpkgs/nixos/tests/maddy.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "maddy";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    server = { ... }: {
+      services.maddy = {
+        enable = true;
+        hostname = "server";
+        primaryDomain = "server";
+        openFirewall = true;
+      };
+    };
+
+    client = { ... }: {
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          from email.mime.text import MIMEText
+
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "postmaster@server"
+          msg['To'] = "postmaster@server"
+          with smtplib.SMTP('server', 587) as smtp:
+              smtp.login('postmaster@server', 'test')
+              smtp.sendmail('postmaster@server', 'postmaster@server', msg.as_string())
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+
+          with imaplib.IMAP4('server') as imap:
+              imap.login('postmaster@server', 'test')
+              imap.select()
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+        '')
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("maddy.service")
+    server.wait_for_open_port(143)
+    server.wait_for_open_port(587)
+
+    server.succeed("echo test | maddyctl creds create postmaster@server")
+    server.succeed("maddyctl imap-acct create postmaster@server")
+
+    client.succeed("send-testmail")
+    client.succeed("test-imap")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix b/nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix
index afdf7124fdc5..54088ac60f28 100644
--- a/nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/man.nix b/nixpkgs/nixos/tests/man.nix
new file mode 100644
index 000000000000..1ff5af4e8059
--- /dev/null
+++ b/nixpkgs/nixos/tests/man.nix
@@ -0,0 +1,100 @@
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  manImplementations = [
+    "mandoc"
+    "man-db"
+  ];
+
+  machineNames = builtins.map machineSafe manImplementations;
+
+  makeConfig = useImpl: {
+    # Note: mandoc currently can't index symlinked section directories.
+    # So if a man section comes from one package exclusively (e. g.
+    # 1p from man-pages-posix and 2 from man-pages), it isn't searchable.
+    environment.systemPackages = [
+      pkgs.man-pages
+      pkgs.openssl
+      pkgs.libunwind
+    ];
+
+    documentation = {
+      enable = true;
+      nixos.enable = lib.mkForce true;
+      dev.enable = true;
+      man = {
+        enable = true;
+        generateCaches = true;
+      } // lib.listToAttrs (builtins.map (impl: {
+        name = impl;
+        value = {
+          enable = useImpl == impl;
+        };
+      }) manImplementations);
+    };
+  };
+
+  machineSafe = builtins.replaceStrings [ "-" ] [ "_" ];
+in {
+  name = "man";
+  meta.maintainers = [ lib.maintainers.sternenseemann ];
+
+  nodes = lib.listToAttrs (builtins.map (i: {
+    name = machineSafe i;
+    value = makeConfig i;
+  }) manImplementations);
+
+  testScript = ''
+    import re
+    start_all()
+
+    def match_man_k(page, section, haystack):
+      """
+      Check if the man page {page}({section}) occurs in
+      the output of `man -k` given as haystack. Note:
+      This is not super reliable, e. g. it can't deal
+      with man pages that are in multiple sections.
+      """
+
+      for line in haystack.split("\n"):
+        # man -k can look like this:
+        # page(3) - bla
+        # page (3) - bla
+        # pagea, pageb (3, 3P) - foo
+        # pagea, pageb, pagec(3) - bar
+        pages = line.split("(")[0]
+        sections = re.search("\\([a-zA-Z1-9, ]+\\)", line)
+        if sections is None:
+          continue
+        else:
+          sections = sections.group(0)[1:-1]
+
+        if page in pages and f'{section}' in sections:
+          return True
+
+      return False
+
+  '' + lib.concatMapStrings (machine: ''
+    with subtest("Test direct man page lookups in ${machine}"):
+      # man works
+      ${machine}.succeed("man man > /dev/null")
+      # devman works
+      ${machine}.succeed("man 3 libunwind > /dev/null")
+      # NixOS configuration man page is installed
+      ${machine}.succeed("man configuration.nix > /dev/null")
+
+    with subtest("Test generateCaches via man -k in ${machine}"):
+      expected = [
+        ("openssl", "ssl", 3),
+        ("unwind", "libunwind", 3),
+        ("user", "useradd", 8),
+        ("user", "userdel", 8),
+        ("mem", "free", 3),
+        ("mem", "free", 1),
+      ]
+
+      for (keyword, page, section) in expected:
+        matches = ${machine}.succeed(f"man -k {keyword}")
+        if not match_man_k(page, section, matches):
+          raise Exception(f"{page}({section}) missing in matches: {matches}")
+  '') machineNames;
+})
diff --git a/nixpkgs/nixos/tests/matrix-appservice-irc.nix b/nixpkgs/nixos/tests/matrix-appservice-irc.nix
index 79b07ef83c57..e1da410af060 100644
--- a/nixpkgs/nixos/tests/matrix-appservice-irc.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/matrix/mjolnir.nix b/nixpkgs/nixos/tests/matrix/mjolnir.nix
new file mode 100644
index 000000000000..bb55f6f5440b
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/matrix/pantalaimon.nix b/nixpkgs/nixos/tests/matrix/pantalaimon.nix
new file mode 100644
index 000000000000..fcb9904b2138
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/mattermost.nix b/nixpkgs/nixos/tests/mattermost.nix
new file mode 100644
index 000000000000..49b418d9fff7
--- /dev/null
+++ b/nixpkgs/nixos/tests/mattermost.nix
@@ -0,0 +1,124 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  host = "smoke.test";
+  port = "8065";
+  url = "http://${host}:${port}";
+  siteName = "NixOS Smoke Tests, Inc.";
+
+  makeMattermost = mattermostConfig:
+    { config, ... }: {
+      environment.systemPackages = [
+        pkgs.mattermost
+        pkgs.curl
+        pkgs.jq
+      ];
+      networking.hosts = {
+        "127.0.0.1" = [ host ];
+      };
+      services.mattermost = lib.recursiveUpdate {
+        enable = true;
+        inherit siteName;
+        listenAddress = "0.0.0.0:${port}";
+        siteUrl = url;
+        extraConfig = {
+          SupportSettings.AboutLink = "https://nixos.org";
+        };
+      } mattermostConfig;
+    };
+in
+{
+  name = "mattermost";
+
+  nodes = {
+    mutable = makeMattermost {
+      mutableConfig = true;
+      extraConfig.SupportSettings.HelpLink = "https://search.nixos.org";
+    };
+    mostlyMutable = makeMattermost {
+      mutableConfig = true;
+      preferNixConfig = true;
+      plugins = let
+        mattermostDemoPlugin = pkgs.fetchurl {
+          url = "https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.9.0/com.mattermost.demo-plugin-0.9.0.tar.gz";
+          sha256 = "1h4qi34gcxcx63z8wiqcf2aaywmvv8lys5g8gvsk13kkqhlmag25";
+        };
+      in [
+        mattermostDemoPlugin
+      ];
+    };
+    immutable = makeMattermost {
+      mutableConfig = false;
+      extraConfig.SupportSettings.HelpLink = "https://search.nixos.org";
+    };
+  };
+
+  testScript = let
+    expectConfig = jqExpression: pkgs.writeShellScript "expect-config" ''
+      set -euo pipefail
+      echo "Expecting config to match: "${lib.escapeShellArg jqExpression} >&2
+      curl ${lib.escapeShellArg url} >/dev/null
+      config="$(curl ${lib.escapeShellArg "${url}/api/v4/config/client?format=old"})"
+      echo "Config: $(echo "$config" | ${pkgs.jq}/bin/jq)" >&2
+      [[ "$(echo "$config" | ${pkgs.jq}/bin/jq -r ${lib.escapeShellArg ".SiteName == $siteName and .Version == ($mattermostName / $sep)[-1] and (${jqExpression})"} --arg siteName ${lib.escapeShellArg siteName} --arg mattermostName ${lib.escapeShellArg pkgs.mattermost.name} --arg sep '-')" = "true" ]]
+    '';
+
+    setConfig = jqExpression: pkgs.writeShellScript "set-config" ''
+      set -euo pipefail
+      mattermostConfig=/var/lib/mattermost/config/config.json
+      newConfig="$(${pkgs.jq}/bin/jq -r ${lib.escapeShellArg jqExpression} $mattermostConfig)"
+      rm -f $mattermostConfig
+      echo "$newConfig" > "$mattermostConfig"
+    '';
+  in
+  ''
+    start_all()
+
+    ## Mutable node tests ##
+    mutable.wait_for_unit("mattermost.service")
+    mutable.wait_for_open_port(8065)
+
+    # Get the initial config
+    mutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+
+    # Edit the config
+    mutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
+    mutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
+    mutable.systemctl("restart mattermost.service")
+    mutable.wait_for_open_port(8065)
+
+    # AboutLink and HelpLink should be changed
+    mutable.succeed("${expectConfig ''.AboutLink == "https://mattermost.com" and .HelpLink == "https://nixos.org/nixos/manual"''}")
+
+    ## Mostly mutable node tests ##
+    mostlyMutable.wait_for_unit("mattermost.service")
+    mostlyMutable.wait_for_open_port(8065)
+
+    # Get the initial config
+    mostlyMutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org"''}")
+
+    # Edit the config
+    mostlyMutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
+    mostlyMutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
+    mostlyMutable.systemctl("restart mattermost.service")
+    mostlyMutable.wait_for_open_port(8065)
+
+    # AboutLink should be overridden by NixOS configuration; HelpLink should be what we set above
+    mostlyMutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://nixos.org/nixos/manual"''}")
+
+    ## Immutable node tests ##
+    immutable.wait_for_unit("mattermost.service")
+    immutable.wait_for_open_port(8065)
+
+    # Get the initial config
+    immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+
+    # Edit the config
+    immutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
+    immutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
+    immutable.systemctl("restart mattermost.service")
+    immutable.wait_for_open_port(8065)
+
+    # Our edits should be ignored on restart
+    immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/metabase.nix b/nixpkgs/nixos/tests/metabase.nix
index 370114e92223..1b25071902e9 100644
--- a/nixpkgs/nixos/tests/metabase.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/minecraft.nix b/nixpkgs/nixos/tests/minecraft.nix
index 3225ebac392a..1c34f04b4df2 100644
--- a/nixpkgs/nixos/tests/minecraft.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/misc.nix b/nixpkgs/nixos/tests/misc.nix
index fb19b7060562..0587912c9a22 100644
--- a/nixpkgs/nixos/tests/misc.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/moinmoin.nix b/nixpkgs/nixos/tests/moinmoin.nix
deleted file mode 100644
index ac327498eba0..000000000000
--- a/nixpkgs/nixos/tests/moinmoin.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
-  name = "moinmoin";
-  meta.maintainers = with lib.maintainers; [ mmilata ];
-
-  machine =
-    { ... }:
-    { services.moinmoin.enable = true;
-      services.moinmoin.wikis.ExampleWiki.superUsers = [ "admin" ];
-      services.moinmoin.wikis.ExampleWiki.webHost = "localhost";
-
-      services.nginx.virtualHosts.localhost.enableACME = false;
-      services.nginx.virtualHosts.localhost.forceSSL = false;
-    };
-
-  testScript = ''
-    start_all()
-
-    machine.wait_for_unit("moin-ExampleWiki.service")
-    machine.wait_for_unit("nginx.service")
-    machine.wait_for_file("/run/moin/ExampleWiki/gunicorn.sock")
-
-    assert "If you have just installed" in machine.succeed("curl -L http://localhost/")
-
-    assert "status success" in machine.succeed(
-        "moin-ExampleWiki account create --name=admin --email=admin@example.com --password=foo 2>&1"
-    )
-  '';
-})
diff --git a/nixpkgs/nixos/tests/mosquitto.nix b/nixpkgs/nixos/tests/mosquitto.nix
index e29bd559ed9b..36cc8e3e3d9b 100644
--- a/nixpkgs/nixos/tests/mosquitto.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/mpv.nix b/nixpkgs/nixos/tests/mpv.nix
index 9e44862cb1b4..a4803f3cb5b5 100644
--- a/nixpkgs/nixos/tests/mpv.nix
+++ b/nixpkgs/nixos/tests/mpv.nix
@@ -21,7 +21,7 @@ in
     };
 
   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/nixpkgs/nixos/tests/mumble.nix b/nixpkgs/nixos/tests/mumble.nix
index 717f3c789288..2b5cc20163bc 100644
--- a/nixpkgs/nixos/tests/mumble.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/musescore.nix b/nixpkgs/nixos/tests/musescore.nix
index 96481a9a8bf4..7fd80d70df12 100644
--- a/nixpkgs/nixos/tests/musescore.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/mysql/mysql.nix b/nixpkgs/nixos/tests/mysql/mysql.nix
index dce5fa26acf7..2ac2b34a18e2 100644
--- a/nixpkgs/nixos/tests/mysql/mysql.nix
+++ b/nixpkgs/nixos/tests/mysql/mysql.nix
@@ -64,10 +64,6 @@ in
       {
         imports = [ users ];
 
-        # prevent oom:
-        # Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
-        virtualisation.memorySize = 1024;
-
         services.mysql.enable = true;
         services.mysql.initialDatabases = [
           { name = "testdb3"; schema = ./testdb.sql; }
diff --git a/nixpkgs/nixos/tests/networking-proxy.nix b/nixpkgs/nixos/tests/networking-proxy.nix
index 62b5e690f6d1..fcb2558cf3b0 100644
--- a/nixpkgs/nixos/tests/networking-proxy.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/networking.nix b/nixpkgs/nixos/tests/networking.nix
index 647c8942b37d..f46a115a07d4 100644
--- a/nixpkgs/nixos/tests/networking.nix
+++ b/nixpkgs/nixos/tests/networking.nix
@@ -489,6 +489,77 @@ let
               client2.wait_until_succeeds("ping -c 1 fc00::2")
         '';
     };
+    gre = let
+      node = { pkgs, ... }: with pkgs.lib; {
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+        };
+      };
+    in {
+      name = "GRE";
+      nodes.client1 = args@{ pkgs, ... }:
+        mkMerge [
+          (node args)
+          {
+            virtualisation.vlans = [ 1 2 ];
+            networking = {
+              greTunnels = {
+                greTunnel = {
+                  local = "192.168.2.1";
+                  remote = "192.168.2.2";
+                  dev = "eth2";
+                  type = "tap";
+                };
+              };
+              bridges.bridge.interfaces = [ "greTunnel" "eth1" ];
+              interfaces.eth1.ipv4.addresses = mkOverride 0 [];
+              interfaces.bridge.ipv4.addresses = mkOverride 0 [
+                { address = "192.168.1.1"; prefixLength = 24; }
+              ];
+            };
+          }
+        ];
+      nodes.client2 = args@{ pkgs, ... }:
+        mkMerge [
+          (node args)
+          {
+            virtualisation.vlans = [ 2 3 ];
+            networking = {
+              greTunnels = {
+                greTunnel = {
+                  local = "192.168.2.2";
+                  remote = "192.168.2.1";
+                  dev = "eth1";
+                  type = "tap";
+                };
+              };
+              bridges.bridge.interfaces = [ "greTunnel" "eth2" ];
+              interfaces.eth2.ipv4.addresses = mkOverride 0 [];
+              interfaces.bridge.ipv4.addresses = mkOverride 0 [
+                { address = "192.168.1.2"; prefixLength = 24; }
+              ];
+            };
+          }
+        ];
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to be configured"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
+
+              # Print diagnostic information
+              client1.succeed("ip addr >&2")
+              client2.succeed("ip addr >&2")
+
+          with subtest("Test GRE tunnel bridge over VLAN"):
+              client1.wait_until_succeeds("ping -c 1 192.168.1.2")
+
+              client2.wait_until_succeeds("ping -c 1 192.168.1.1")
+        '';
+    };
     vlan = let
       node = address: { pkgs, ... }: with pkgs.lib; {
         #virtualisation.vlans = [ 1 ];
diff --git a/nixpkgs/nixos/tests/nextcloud/default.nix b/nixpkgs/nixos/tests/nextcloud/default.nix
index 65043e509b3b..34d3c345354c 100644
--- a/nixpkgs/nixos/tests/nextcloud/default.nix
+++ b/nixpkgs/nixos/tests/nextcloud/default.nix
@@ -1,6 +1,6 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../../.. { inherit system config; }
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
 }:
 
 with pkgs.lib;
@@ -17,5 +17,5 @@ foldl
       nextcloudVersion = ver;
     };
   })
-  {}
-  [ 20 21 22 ]
+{ }
+  [ 21 22 23 ]
diff --git a/nixpkgs/nixos/tests/nfs/simple.nix b/nixpkgs/nixos/tests/nfs/simple.nix
index 6a01089c0828..1e319a8eec81 100644
--- a/nixpkgs/nixos/tests/nfs/simple.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/nginx-etag.nix b/nixpkgs/nixos/tests/nginx-etag.nix
index 63ab2e0c6c27..b69511d081d4 100644
--- a/nixpkgs/nixos/tests/nginx-etag.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/nix-ssh-serve.nix b/nixpkgs/nixos/tests/nix-serve-ssh.nix
index 03f83542c7c1..1eb8d5b395b1 100644
--- a/nixpkgs/nixos/tests/nix-ssh-serve.nix
+++ b/nixpkgs/nixos/tests/nix-serve-ssh.nix
@@ -35,7 +35,7 @@ in
 
        client.fail("diff /root/other-store$(cat mach-id-path) /etc/machine-id")
        # Currently due to shared store this is a noop :(
-       client.succeed("nix copy --to ssh-ng://nix-ssh@server $(cat mach-id-path)")
+       client.succeed("nix copy --experimental-features 'nix-command' --to ssh-ng://nix-ssh@server $(cat mach-id-path)")
        client.succeed(
            "nix-store --realise $(cat mach-id-path) --store /root/other-store --substituters ssh-ng://nix-ssh@server"
        )
diff --git a/nixpkgs/nixos/tests/nixops/default.nix b/nixpkgs/nixos/tests/nixops/default.nix
index 4520b426849b..ec3d028aabae 100644
--- a/nixpkgs/nixos/tests/nixops/default.nix
+++ b/nixpkgs/nixos/tests/nixops/default.nix
@@ -26,8 +26,7 @@ let
         nix.binaryCaches = lib.mkForce [ ];
         users.users.person.isNormalUser = true;
         virtualisation.writableStore = true;
-        virtualisation.memorySize = 1024 /*MiB*/;
-        virtualisation.pathsInNixDB = [
+        virtualisation.additionalPaths = [
           pkgs.hello
           pkgs.figlet
 
@@ -78,7 +77,7 @@ let
         # 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] >/dev/console; done &")
+        deployer.succeed("while sleep 60s; do echo [60s passed]; done >&2 &")
 
         deployer_do("cd ~/unicorn; ssh -oStrictHostKeyChecking=accept-new root@server echo hi")
 
diff --git a/nixpkgs/nixos/tests/odoo.nix b/nixpkgs/nixos/tests/odoo.nix
new file mode 100644
index 000000000000..96e3405482b4
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/openarena.nix b/nixpkgs/nixos/tests/openarena.nix
index 461a35e89fe7..63dc1b9a6857 100644
--- a/nixpkgs/nixos/tests/openarena.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/openresty-lua.nix b/nixpkgs/nixos/tests/openresty-lua.nix
new file mode 100644
index 000000000000..b177b3c194d7
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/opensmtpd-rspamd.nix b/nixpkgs/nixos/tests/opensmtpd-rspamd.nix
index 9cb2624e6c4e..19969a7b47dd 100644
--- a/nixpkgs/nixos/tests/opensmtpd-rspamd.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/os-prober.nix b/nixpkgs/nixos/tests/os-prober.nix
index 3cc38ebe3471..c1e29b0f68b4 100644
--- a/nixpkgs/nixos/tests/os-prober.nix
+++ b/nixpkgs/nixos/tests/os-prober.nix
@@ -53,14 +53,14 @@ let
   };
   # /etc/nixos/configuration.nix for the vm
   configFile = pkgs.writeText "configuration.nix"  ''
-    {config, pkgs, ...}: ({
+    {config, pkgs, lib, ...}: ({
     imports =
           [ ./hardware-configuration.nix
             <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
           ];
-    } // (builtins.fromJSON (builtins.readFile ${
+    } // lib.importJSON ${
       pkgs.writeText "simpleConfig.json" (builtins.toJSON simpleConfig)
-    })))
+    })
   '';
 in {
   name = "os-prober";
@@ -114,7 +114,7 @@ in {
         "${configFile}",
         "/etc/nixos/configuration.nix",
     )
-    machine.succeed("nixos-rebuild boot >&2")
+    machine.succeed("nixos-rebuild boot --show-trace >&2")
 
     machine.succeed("egrep 'menuentry.*debian' /boot/grub/grub.cfg")
   '';
diff --git a/nixpkgs/nixos/tests/owncast.nix b/nixpkgs/nixos/tests/owncast.nix
index e54d2cc5dd48..debb34f5009d 100644
--- a/nixpkgs/nixos/tests/owncast.nix
+++ b/nixpkgs/nixos/tests/owncast.nix
@@ -1,21 +1,42 @@
-{ system ? builtins.currentSystem, config ? { }
-, pkgs ? import ../.. { inherit system config; } }:
-
-with import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; };
-makeTest {
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "owncast";
-  meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ MayNiklas ]; };
+  meta = with pkgs.lib.maintainers; { maintainers = [ MayNiklas ]; };
 
   nodes = {
-    client = { ... }: {
-      environment.systemPackages = [ curl ];
-      services.owncast = { enable = true; };
+    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("owncast.service")
-    client.succeed("curl localhost:8080/api/status")
+
+    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/nixpkgs/nixos/tests/pam/pam-file-contents.nix b/nixpkgs/nixos/tests/pam/pam-file-contents.nix
new file mode 100644
index 000000000000..86c61003aeb6
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/pam-file-contents.nix
@@ -0,0 +1,25 @@
+let
+  name = "pam";
+in
+import ../make-test-python.nix ({ pkgs, ... }: {
+
+  nodes.machine = { ... }: {
+    imports = [ ../../modules/profiles/minimal.nix ];
+
+    krb5.enable = true;
+
+    users = {
+      mutableUsers = false;
+      users = {
+        user = {
+          isNormalUser = true;
+        };
+      };
+    };
+  };
+
+  testScript = builtins.replaceStrings
+    [ "@@pam_ccreds@@" "@@pam_krb5@@" ]
+    [ pkgs.pam_ccreds.outPath pkgs.pam_krb5.outPath ]
+    (builtins.readFile ./test_chfn.py);
+})
diff --git a/nixpkgs/nixos/tests/pam-oath-login.nix b/nixpkgs/nixos/tests/pam/pam-oath-login.nix
index 6d48199eda97..597596b211b1 100644
--- a/nixpkgs/nixos/tests/pam-oath-login.nix
+++ b/nixpkgs/nixos/tests/pam/pam-oath-login.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ ... }:
+import ../make-test-python.nix ({ ... }:
 
 let
   oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
diff --git a/nixpkgs/nixos/tests/pam-u2f.nix b/nixpkgs/nixos/tests/pam/pam-u2f.nix
index f492baa9e139..0ac6ac17be82 100644
--- a/nixpkgs/nixos/tests/pam-u2f.nix
+++ b/nixpkgs/nixos/tests/pam/pam-u2f.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ ... }:
+import ../make-test-python.nix ({ ... }:
 
 {
   name = "pam-u2f";
diff --git a/nixpkgs/nixos/tests/pam/test_chfn.py b/nixpkgs/nixos/tests/pam/test_chfn.py
new file mode 100644
index 000000000000..b108a9423caf
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/test_chfn.py
@@ -0,0 +1,27 @@
+expected_lines = {
+    "account required pam_unix.so",
+    "account sufficient @@pam_krb5@@/lib/security/pam_krb5.so",
+    "auth [default=die success=done] @@pam_ccreds@@/lib/security/pam_ccreds.so action=validate use_first_pass",
+    "auth [default=ignore success=1 service_err=reset] @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass",
+    "auth required pam_deny.so",
+    "auth sufficient @@pam_ccreds@@/lib/security/pam_ccreds.so action=store use_first_pass",
+    "auth sufficient pam_rootok.so",
+    "auth sufficient pam_unix.so   likeauth try_first_pass",
+    "password sufficient @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass",
+    "password sufficient pam_unix.so nullok sha512",
+    "session optional @@pam_krb5@@/lib/security/pam_krb5.so",
+    "session required pam_env.so conffile=/etc/pam/environment readenv=0",
+    "session required pam_unix.so",
+}
+actual_lines = set(machine.succeed("cat /etc/pam.d/chfn").splitlines())
+
+missing_lines = expected_lines - actual_lines
+extra_lines = actual_lines - expected_lines
+non_functional_lines = set([line for line in extra_lines if (line == "" or line.startswith("#"))])
+unexpected_functional_lines = extra_lines - non_functional_lines
+
+with subtest("All expected lines are in the file"):
+    assert not missing_lines, f"Missing lines: {missing_lines}"
+
+with subtest("All remaining lines are empty or comments"):
+    assert not unexpected_functional_lines, f"Unexpected lines: {unexpected_functional_lines}"
diff --git a/nixpkgs/nixos/tests/pantheon.nix b/nixpkgs/nixos/tests/pantheon.nix
index 20aee2eb7a4c..989d29a966df 100644
--- a/nixpkgs/nixos/tests/pantheon.nix
+++ b/nixpkgs/nixos/tests/pantheon.nix
@@ -15,7 +15,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     services.xserver.enable = true;
     services.xserver.desktopManager.pantheon.enable = true;
 
-    virtualisation.memorySize = 1024;
   };
 
   enableOCR = true;
@@ -51,7 +50,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         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/nixpkgs/nixos/tests/paperless-ng.nix b/nixpkgs/nixos/tests/paperless-ng.nix
index a4b2f348ec32..618eeec6b125 100644
--- a/nixpkgs/nixos/tests/paperless-ng.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/parsedmarc/default.nix b/nixpkgs/nixos/tests/parsedmarc/default.nix
index d838d3b6a39c..50b977723e9c 100644
--- a/nixpkgs/nixos/tests/parsedmarc/default.nix
+++ b/nixpkgs/nixos/tests/parsedmarc/default.nix
@@ -4,6 +4,7 @@
 { pkgs, ... }@args:
 let
   inherit (import ../../lib/testing-python.nix args) makeTest;
+  inherit (pkgs) lib;
 
   dmarcTestReport = builtins.fetchurl {
     name = "dmarc-test-report";
@@ -54,7 +55,7 @@ in
   localMail = makeTest
     {
       name = "parsedmarc-local-mail";
-      meta = with pkgs.lib.maintainers; {
+      meta = with lib.maintainers; {
         maintainers = [ talyz ];
       };
 
@@ -83,7 +84,7 @@ in
             };
           };
 
-          services.elasticsearch.package = pkgs.elasticsearch7-oss;
+          services.elasticsearch.package = pkgs.elasticsearch-oss;
 
           environment.systemPackages = [
             (sendEmail "dmarc@localhost")
@@ -94,6 +95,7 @@ in
       testScript = { nodes }:
         let
           esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
+          valueObject = lib.optionalString (lib.versionAtLeast nodes.parsedmarc.config.services.elasticsearch.package.version "7") ".value";
         in ''
           parsedmarc.start()
           parsedmarc.wait_for_unit("postfix.service")
@@ -104,11 +106,15 @@ in
           )
 
           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'"
+              "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 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'"
+              "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
           )
         '';
     };
@@ -121,7 +127,7 @@ in
     in
       makeTest {
         name = "parsedmarc-external-mail";
-        meta = with pkgs.lib.maintainers; {
+        meta = with lib.maintainers; {
           maintainers = [ talyz ];
         };
 
@@ -153,7 +159,7 @@ in
                 };
               };
 
-              services.elasticsearch.package = pkgs.elasticsearch7-oss;
+              services.elasticsearch.package = pkgs.elasticsearch-oss;
 
               environment.systemPackages = [
                 pkgs.jq
@@ -201,6 +207,7 @@ in
         testScript = { nodes }:
           let
             esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
+            valueObject = lib.optionalString (lib.versionAtLeast nodes.parsedmarc.config.services.elasticsearch.package.version "7") ".value";
           in ''
             mail.start()
             mail.wait_for_unit("postfix.service")
@@ -213,11 +220,15 @@ in
             )
 
             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'"
+                "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+                + " | tee /dev/console"
+                + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 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'"
+                "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+                + " | tee /dev/console"
+                + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
             )
           '';
       };
diff --git a/nixpkgs/nixos/tests/plasma5-systemd-start.nix b/nixpkgs/nixos/tests/plasma5-systemd-start.nix
new file mode 100644
index 000000000000..72de19af70ce
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/plasma5.nix b/nixpkgs/nixos/tests/plasma5.nix
index 7a5b7db94629..5c7ea602f79e 100644
--- a/nixpkgs/nixos/tests/plasma5.nix
+++ b/nixpkgs/nixos/tests/plasma5.nix
@@ -19,7 +19,6 @@ import ./make-test-python.nix ({ pkgs, ...} :
       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/nixpkgs/nixos/tests/pleroma.nix b/nixpkgs/nixos/tests/pleroma.nix
index d0ae1488d134..bf3623fce38b 100644
--- a/nixpkgs/nixos/tests/pleroma.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/plotinus.nix b/nixpkgs/nixos/tests/plotinus.nix
index ddd6a4c11946..af38b41813b7 100644
--- a/nixpkgs/nixos/tests/plotinus.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/podman.nix b/nixpkgs/nixos/tests/podman/default.nix
index 6184561e6ddd..b52a7f060ad6 100644
--- a/nixpkgs/nixos/tests/podman.nix
+++ b/nixpkgs/nixos/tests/podman/default.nix
@@ -1,6 +1,6 @@
 # This test runs podman and checks if simple container starts
 
-import ./make-test-python.nix (
+import ../make-test-python.nix (
   { pkgs, lib, ... }: {
     name = "podman";
     meta = {
@@ -48,7 +48,7 @@ import ./make-test-python.nix (
       start_all()
 
       with subtest("Run container as root with runc"):
-          podman.succeed("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg")
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
           podman.succeed(
               "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
@@ -57,7 +57,7 @@ import ./make-test-python.nix (
           podman.succeed("podman rm sleeping")
 
       with subtest("Run container as root with crun"):
-          podman.succeed("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg")
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
           podman.succeed(
               "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
@@ -66,7 +66,7 @@ import ./make-test-python.nix (
           podman.succeed("podman rm sleeping")
 
       with subtest("Run container as root with the default backend"):
-          podman.succeed("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg")
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
           podman.succeed(
               "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
@@ -78,7 +78,7 @@ import ./make-test-python.nix (
       podman.succeed("loginctl enable-linger alice")
 
       with subtest("Run container rootless with runc"):
-          podman.succeed(su_cmd("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg"))
+          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
           podman.succeed(
               su_cmd(
                   "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
@@ -89,7 +89,7 @@ import ./make-test-python.nix (
           podman.succeed(su_cmd("podman rm sleeping"))
 
       with subtest("Run container rootless with crun"):
-          podman.succeed(su_cmd("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg"))
+          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
           podman.succeed(
               su_cmd(
                   "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
@@ -100,7 +100,7 @@ import ./make-test-python.nix (
           podman.succeed(su_cmd("podman rm sleeping"))
 
       with subtest("Run container rootless with the default backend"):
-          podman.succeed(su_cmd("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg"))
+          podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
           podman.succeed(
               su_cmd(
                   "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
@@ -112,7 +112,7 @@ import ./make-test-python.nix (
 
       with subtest("Run container with init"):
           podman.succeed(
-              "tar cvf busybox.tar -C ${pkgs.pkgsStatic.busybox} . && podman import busybox.tar busybox"
+              "tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - busybox"
           )
           pid = podman.succeed("podman run --rm busybox readlink /proc/self").strip()
           assert pid == "1"
@@ -124,7 +124,7 @@ import ./make-test-python.nix (
 
       with subtest("Run container via docker cli"):
           podman.succeed("docker network create default")
-          podman.succeed("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg")
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
           podman.succeed(
             "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
           )
diff --git a/nixpkgs/nixos/tests/podman-dnsname.nix b/nixpkgs/nixos/tests/podman/dnsname.nix
index 9e4e8fdb08a2..3768ae79e067 100644
--- a/nixpkgs/nixos/tests/podman-dnsname.nix
+++ b/nixpkgs/nixos/tests/podman/dnsname.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix (
+import ../make-test-python.nix (
   { pkgs, lib, ... }:
   let
     inherit (pkgs) writeTextDir python3 curl;
@@ -21,7 +21,7 @@ import ./make-test-python.nix (
       podman.wait_for_unit("sockets.target")
 
       with subtest("DNS works"): # also tests inter-container tcp routing
-        podman.succeed("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg")
+        podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
         podman.succeed(
           "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${webroot} scratchimg ${python3}/bin/python -m http.server 8000"
         )
diff --git a/nixpkgs/nixos/tests/podman-tls-ghostunnel.nix b/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
index b5836c436497..c0bc47cc40b1 100644
--- a/nixpkgs/nixos/tests/podman-tls-ghostunnel.nix
+++ b/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
@@ -1,7 +1,7 @@
 /*
   This test runs podman as a backend for the Docker CLI.
  */
-import ./make-test-python.nix (
+import ../make-test-python.nix (
   { pkgs, lib, ... }:
 
   let gen-ca = pkgs.writeScript "gen-ca" ''
@@ -126,7 +126,7 @@ import ./make-test-python.nix (
           client.succeed("docker version")
 
           # via socket would be nicer
-          podman.succeed("tar cvf scratchimg.tar --files-from /dev/null && podman import scratchimg.tar scratchimg")
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
 
           client.succeed(
             "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
diff --git a/nixpkgs/nixos/tests/powerdns-admin.nix b/nixpkgs/nixos/tests/powerdns-admin.nix
new file mode 100644
index 000000000000..4d763c9c6f6e
--- /dev/null
+++ b/nixpkgs/nixos/tests/powerdns-admin.nix
@@ -0,0 +1,117 @@
+# Test powerdns-admin
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+let
+  defaultConfig = ''
+    BIND_ADDRESS = '127.0.0.1'
+    PORT = 8000
+  '';
+
+  makeAppTest = name: configs: makeTest {
+    name = "powerdns-admin-${name}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ Flakebi zhaofengli ];
+    };
+
+    nodes.server = { pkgs, config, ... }: mkMerge ([
+      {
+        services.powerdns-admin = {
+          enable = true;
+          secretKeyFile = "/etc/powerdns-admin/secret";
+          saltFile = "/etc/powerdns-admin/salt";
+        };
+        # It's insecure to have secrets in the world-readable nix store, but this is just a test
+        environment.etc."powerdns-admin/secret".text = "secret key";
+        environment.etc."powerdns-admin/salt".text = "salt";
+        environment.systemPackages = [
+          (pkgs.writeShellScriptBin "run-test" config.system.build.testScript)
+        ];
+      }
+    ] ++ configs);
+
+    testScript = ''
+      server.wait_for_unit("powerdns-admin.service")
+      server.wait_until_succeeds("run-test", timeout=10)
+    '';
+  };
+
+  matrix = {
+    backend = {
+      mysql = {
+        services.powerdns-admin = {
+          config = ''
+            ${defaultConfig}
+            SQLALCHEMY_DATABASE_URI = 'mysql://powerdnsadmin@/powerdnsadmin?unix_socket=/run/mysqld/mysqld.sock'
+          '';
+        };
+        systemd.services.powerdns-admin = {
+          after = [ "mysql.service" ];
+          serviceConfig.BindPaths = "/run/mysqld";
+        };
+
+        services.mysql = {
+          enable = true;
+          package = pkgs.mariadb;
+          ensureDatabases = [ "powerdnsadmin" ];
+          ensureUsers = [
+            {
+              name = "powerdnsadmin";
+              ensurePermissions = {
+                "powerdnsadmin.*" = "ALL PRIVILEGES";
+              };
+            }
+          ];
+        };
+      };
+      postgresql = {
+        services.powerdns-admin = {
+          config = ''
+            ${defaultConfig}
+            SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql'
+          '';
+        };
+        systemd.services.powerdns-admin = {
+          after = [ "postgresql.service" ];
+          serviceConfig.BindPaths = "/run/postgresql";
+        };
+
+        services.postgresql = {
+          enable = true;
+          ensureDatabases = [ "powerdnsadmin" ];
+          ensureUsers = [
+            {
+              name = "powerdnsadmin";
+              ensurePermissions = {
+                "DATABASE powerdnsadmin" = "ALL PRIVILEGES";
+              };
+            }
+          ];
+        };
+      };
+    };
+    listen = {
+      tcp = {
+        services.powerdns-admin.extraArgs = [ "-b" "127.0.0.1:8000" ];
+        system.build.testScript = ''
+          curl -sSf http://127.0.0.1:8000/
+        '';
+      };
+      unix = {
+        services.powerdns-admin.extraArgs = [ "-b" "unix:/run/powerdns-admin/http.sock" ];
+        system.build.testScript = ''
+          curl -sSf --unix-socket /run/powerdns-admin/http.sock http://somehost/
+        '';
+      };
+    };
+  };
+in
+with matrix; {
+  postgresql = makeAppTest "postgresql" [ backend.postgresql listen.tcp ];
+  mysql = makeAppTest "mysql" [ backend.mysql listen.tcp ];
+  unix-listener = makeAppTest "unix-listener" [ backend.postgresql listen.unix ];
+}
diff --git a/nixpkgs/nixos/tests/printing.nix b/nixpkgs/nixos/tests/printing.nix
index badcb99a57af..6338fd8d8ac1 100644
--- a/nixpkgs/nixos/tests/printing.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/privacyidea.nix b/nixpkgs/nixos/tests/privacyidea.nix
index 4a94f0727946..c1141465ec24 100644
--- a/nixpkgs/nixos/tests/privacyidea.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/prometheus-exporters.nix b/nixpkgs/nixos/tests/prometheus-exporters.nix
index 38b93c4087c0..036c037e426c 100644
--- a/nixpkgs/nixos/tests/prometheus-exporters.nix
+++ b/nixpkgs/nixos/tests/prometheus-exporters.nix
@@ -259,6 +259,19 @@ let
       '';
     };
 
+    fastly = {
+      exporterConfig = {
+        enable = true;
+        tokenPath = pkgs.writeText "token" "abc123";
+      };
+
+      # noop: fastly's exporter can't start without first talking to fastly
+      # see: https://github.com/peterbourgon/fastly-exporter/issues/87
+      exporterTest = ''
+        succeed("true");
+      '';
+    };
+
     fritzbox = {
       # TODO add proper test case
       exporterConfig = {
@@ -464,7 +477,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 = {
@@ -862,6 +874,9 @@ let
         wait_for_unit("prometheus-postfix-exporter.service")
         wait_for_file("/var/lib/postfix/queue/public/showq")
         wait_for_open_port(9154)
+        wait_until_succeeds(
+            "curl -sSf http://localhost:9154/metrics | grep 'postfix_up{path=\"/var/lib/postfix/queue/public/showq\"} 1'"
+        )
         succeed(
             "curl -sSf http://localhost:9154/metrics | grep 'postfix_smtpd_connects_total 0'"
         )
@@ -937,7 +952,7 @@ let
       exporterConfig = {
         enable = true;
       };
-      metricProvider.services.redis.enable = true;
+      metricProvider.services.redis.servers."".enable = true;
       exporterTest = ''
         wait_for_unit("redis.service")
         wait_for_unit("prometheus-redis-exporter.service")
@@ -953,7 +968,6 @@ let
       };
       metricProvider = {
         services.rspamd.enable = true;
-        virtualisation.memorySize = 1024;
       };
       exporterTest = ''
         wait_for_unit("rspamd.service")
@@ -1017,6 +1031,25 @@ let
       '';
     };
 
+    smartctl = {
+      exporterConfig = {
+        enable = true;
+        devices = [
+          "/dev/vda"
+        ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-smartctl-exporter.service")
+        wait_for_open_port("9633")
+        wait_until_succeeds(
+          "curl -sSf 'localhost:9633/metrics'"
+        )
+        wait_until_succeeds(
+            'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "/dev/vda: Unable to detect device type"'
+        )
+      '';
+    };
+
     smokeping = {
       exporterConfig = {
         enable = true;
diff --git a/nixpkgs/nixos/tests/prometheus.nix b/nixpkgs/nixos/tests/prometheus.nix
index 70ac78a4a468..a075cfc1f1b7 100644
--- a/nixpkgs/nixos/tests/prometheus.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/pt2-clone.nix b/nixpkgs/nixos/tests/pt2-clone.nix
index 3c090b7de428..364920c39871 100644
--- a/nixpkgs/nixos/tests/pt2-clone.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/pulseaudio.nix b/nixpkgs/nixos/tests/pulseaudio.nix
new file mode 100644
index 000000000000..4e2ce679acd7
--- /dev/null
+++ b/nixpkgs/nixos/tests/pulseaudio.nix
@@ -0,0 +1,71 @@
+let
+  mkTest = { systemWide ? false }:
+    import ./make-test-python.nix ({ pkgs, lib, ... }:
+      let
+        testFile = pkgs.fetchurl {
+          url =
+            "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3";
+          hash = "sha256-+iggJW8s0/LfA/okfXsB550/55Q0Sq3OoIzuBrzOPJQ=";
+        };
+
+        makeTestPlay = key:
+          { sox, alsa-utils }:
+          pkgs.writeScriptBin key ''
+            set -euxo pipefail
+            ${sox}/bin/play ${testFile}
+            ${sox}/bin/sox ${testFile} -t wav - | ${alsa-utils}/bin/aplay
+            touch /tmp/${key}_success
+          '';
+
+        testers = builtins.mapAttrs makeTestPlay {
+          testPlay = { inherit (pkgs) sox alsa-utils; };
+          testPlay32 = { inherit (pkgs.pkgsi686Linux) sox alsa-utils; };
+        };
+      in {
+        name = "pulseaudio${lib.optionalString systemWide "-systemWide"}";
+        meta = with pkgs.lib.maintainers; {
+          maintainers = [ synthetica ] ++ pkgs.pulseaudio.meta.maintainers;
+        };
+
+        machine = { ... }:
+
+          {
+            imports = [ ./common/wayland-cage.nix ];
+            hardware.pulseaudio = {
+              enable = true;
+              support32Bit = true;
+              inherit systemWide;
+            };
+
+            environment.systemPackages = [ testers.testPlay pkgs.pavucontrol ]
+              ++ lib.optional pkgs.stdenv.isx86_64 testers.testPlay32;
+          } // lib.optionalAttrs systemWide {
+            users.users.alice.extraGroups = [ "audio" ];
+            systemd.services.pulseaudio.wantedBy = [ "multi-user.target" ];
+          };
+
+        enableOCR = true;
+
+        testScript = { ... }: ''
+          machine.wait_until_succeeds("pgrep xterm")
+          machine.wait_for_text("alice@machine")
+
+          machine.send_chars("testPlay \n")
+          machine.wait_for_file("/tmp/testPlay_success")
+          ${lib.optionalString pkgs.stdenv.isx86_64 ''
+            machine.send_chars("testPlay32 \n")
+            machine.wait_for_file("/tmp/testPlay32_success")
+          ''}
+          machine.screenshot("testPlay")
+
+          # Pavucontrol only loads when Pulseaudio is running. If it isn't, the
+          # text "Playback" (one of the tabs) will never show.
+          machine.send_chars("pavucontrol\n")
+          machine.wait_for_text("Playback")
+          machine.screenshot("Pavucontrol")
+        '';
+      });
+in builtins.mapAttrs (key: val: mkTest val) {
+  user = { systemWide = false; };
+  system = { systemWide = true; };
+}
diff --git a/nixpkgs/nixos/tests/rasdaemon.nix b/nixpkgs/nixos/tests/rasdaemon.nix
new file mode 100644
index 000000000000..e4bd8d96a8d5
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/redis.nix b/nixpkgs/nixos/tests/redis.nix
index 28b6058c2c02..7b70c239ad6e 100644
--- a/nixpkgs/nixos/tests/redis.nix
+++ b/nixpkgs/nixos/tests/redis.nix
@@ -1,7 +1,4 @@
 import ./make-test-python.nix ({ pkgs, ... }:
-let
-  redisSocket = "/run/redis/redis.sock";
-in
 {
   name = "redis";
   meta = with pkgs.lib.maintainers; {
@@ -10,35 +7,40 @@ in
 
   nodes = {
     machine =
-      { pkgs, ... }:
+      { pkgs, lib, ... }: with lib;
 
       {
-        services.redis.enable = true;
-        services.redis.unixSocket = redisSocket;
+        services.redis.servers."".enable = true;
+        services.redis.servers."test".enable = true;
 
-        # Allow access to the unix socket for the "redis" group.
-        services.redis.unixSocketPerm = 770;
-
-        users.users."member" = {
+        users.users = listToAttrs (map (suffix: nameValuePair "member${suffix}" {
           createHome = false;
-          description = "A member of the redis group";
+          description = "A member of the redis${suffix} group";
           isNormalUser = true;
-          extraGroups = [
-            "redis"
-          ];
-        };
+          extraGroups = [ "redis${suffix}" ];
+        }) ["" "-test"]);
       };
   };
 
-  testScript = ''
+  testScript = { nodes, ... }: let
+    inherit (nodes.machine.config.services) redis;
+    in ''
     start_all()
     machine.wait_for_unit("redis")
+    machine.wait_for_unit("redis-test")
+
+    # The unnamed Redis server still opens a port for backward-compatibility
     machine.wait_for_open_port("6379")
 
+    machine.wait_for_file("${redis.servers."".unixSocket}")
+    machine.wait_for_file("${redis.servers."test".unixSocket}")
+
     # The unix socket is accessible to the redis group
     machine.succeed('su member -c "redis-cli ping | grep PONG"')
+    machine.succeed('su member-test -c "redis-cli ping | grep PONG"')
 
     machine.succeed("redis-cli ping | grep PONG")
-    machine.succeed("redis-cli -s ${redisSocket} ping | grep PONG")
+    machine.succeed("redis-cli -s ${redis.servers."".unixSocket} ping | grep PONG")
+    machine.succeed("redis-cli -s ${redis.servers."test".unixSocket} ping | grep PONG")
   '';
 })
diff --git a/nixpkgs/nixos/tests/rspamd.nix b/nixpkgs/nixos/tests/rspamd.nix
index 3fd55444fd8a..f0ccfe7ea0e6 100644
--- a/nixpkgs/nixos/tests/rspamd.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/sabnzbd.nix b/nixpkgs/nixos/tests/sabnzbd.nix
new file mode 100644
index 000000000000..fb35b212b493
--- /dev/null
+++ b/nixpkgs/nixos/tests/sabnzbd.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sabnzbd";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ jojosch ];
+  };
+
+  machine = { pkgs, ... }: {
+    services.sabnzbd = {
+      enable = true;
+    };
+
+    # unrar is unfree
+    nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "unrar" ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sabnzbd.service")
+    machine.wait_until_succeeds(
+        "curl --fail -L http://localhost:8080/"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/samba-wsdd.nix b/nixpkgs/nixos/tests/samba-wsdd.nix
index e7dd17c089a3..0e3185b0c684 100644
--- a/nixpkgs/nixos/tests/samba-wsdd.nix
+++ b/nixpkgs/nixos/tests/samba-wsdd.nix
@@ -38,7 +38,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     server_wsdd.wait_for_unit("samba-wsdd")
 
     client_wsdd.wait_until_succeeds(
-        "echo list | ${pkgs.libressl.nc}/bin/nc -U /run/wsdd/wsdd.sock | grep -i SERVER-WSDD"
+        "echo list | ${pkgs.libressl.nc}/bin/nc -N -U /run/wsdd/wsdd.sock | grep -i SERVER-WSDD"
     )
   '';
 })
diff --git a/nixpkgs/nixos/tests/seafile.nix b/nixpkgs/nixos/tests/seafile.nix
new file mode 100644
index 000000000000..6eec8b1fbe55
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/service-runner.nix b/nixpkgs/nixos/tests/service-runner.nix
index 58f46735f56d..79d96f739a6c 100644
--- a/nixpkgs/nixos/tests/service-runner.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/shattered-pixel-dungeon.nix b/nixpkgs/nixos/tests/shattered-pixel-dungeon.nix
index d8c4b44819e4..d4e5de22ab9d 100644
--- a/nixpkgs/nixos/tests/shattered-pixel-dungeon.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/signal-desktop.nix b/nixpkgs/nixos/tests/signal-desktop.nix
index 379af4d3912b..8c7230629923 100644
--- a/nixpkgs/nixos/tests/signal-desktop.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/snapcast.nix b/nixpkgs/nixos/tests/snapcast.nix
index 8d960b4cc069..30b8343e2ffe 100644
--- a/nixpkgs/nixos/tests/snapcast.nix
+++ b/nixpkgs/nixos/tests/snapcast.nix
@@ -40,6 +40,7 @@ in {
           };
         };
       };
+      environment.systemPackages = [ pkgs.snapcast ];
     };
     client = {
       environment.systemPackages = [ pkgs.snapcast ];
@@ -71,6 +72,13 @@ in {
             "curl --fail http://localhost:${toString httpPort}/jsonrpc -d '{json.dumps(get_rpc_version)}'"
         )
 
+    with subtest("test a ipv6 connection"):
+        server.execute("systemd-run --unit=snapcast-local-client snapclient -h ::1 -p ${toString port}")
+        server.wait_until_succeeds(
+            "journalctl -o cat -u snapserver.service | grep -q 'Hello from'"
+        )
+        server.wait_until_succeeds("journalctl -o cat -u snapcast-local-client | grep -q 'buffer: ${toString bufferSize}'")
+
     with subtest("test a connection"):
         client.execute("systemd-run --unit=snapcast-client snapclient -h server -p ${toString port}")
         server.wait_until_succeeds(
diff --git a/nixpkgs/nixos/tests/soapui.nix b/nixpkgs/nixos/tests/soapui.nix
index 205128df91f4..76a87ed5efa1 100644
--- a/nixpkgs/nixos/tests/soapui.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/sourcehut.nix b/nixpkgs/nixos/tests/sourcehut.nix
index b56a14ebf85e..d1536c593225 100644
--- a/nixpkgs/nixos/tests/sourcehut.nix
+++ b/nixpkgs/nixos/tests/sourcehut.nix
@@ -1,29 +1,197 @@
-import ./make-test-python.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  domain = "sourcehut.localdomain";
 
+  # Note that wildcard certificates just under the TLD (eg. *.com)
+  # would be rejected by clients like curl.
+  tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+      -subj '/CN=${domain}' -extensions v3_req \
+      -addext 'subjectAltName = DNS:*.${domain}'
+    install -D -t $out key.pem cert.pem
+  '';
+
+  images = {
+    nixos.unstable.x86_64 =
+      let
+        systemConfig = { pkgs, ... }: {
+          # passwordless ssh server
+          services.openssh = {
+            enable = true;
+            permitRootLogin = "yes";
+            extraConfig = "PermitEmptyPasswords yes";
+          };
+
+          users = {
+            mutableUsers = false;
+            # build user
+            extraUsers."build" = {
+              isNormalUser = true;
+              uid = 1000;
+              extraGroups = [ "wheel" ];
+              password = "";
+            };
+            users.root.password = "";
+          };
+
+          security.sudo.wheelNeedsPassword = false;
+          nix.trustedUsers = [ "root" "build" ];
+          documentation.nixos.enable = false;
+
+          # builds.sr.ht-image-specific network settings
+          networking = {
+            hostName = "build";
+            dhcpcd.enable = false;
+            defaultGateway.address = "10.0.2.2";
+            usePredictableInterfaceNames = false;
+            interfaces."eth0".ipv4.addresses = [{
+              address = "10.0.2.15";
+              prefixLength = 25;
+            }];
+            enableIPv6 = false;
+            nameservers = [
+              # OpenNIC anycast
+              "185.121.177.177"
+              "169.239.202.202"
+              # Google
+              "8.8.8.8"
+            ];
+            firewall.allowedTCPPorts = [ 22 ];
+          };
+
+          environment.systemPackages = [
+            pkgs.gitMinimal
+            #pkgs.mercurial
+            pkgs.curl
+            pkgs.gnupg
+          ];
+        };
+        qemuConfig = { pkgs, ... }: {
+          imports = [ systemConfig ];
+          fileSystems."/".device = "/dev/disk/by-label/nixos";
+          boot.initrd.availableKernelModules = [
+            "ahci"
+            "ehci_pci"
+            "sd_mod"
+            "usb_storage"
+            "usbhid"
+            "virtio_balloon"
+            "virtio_blk"
+            "virtio_pci"
+            "virtio_ring"
+            "xhci_pci"
+          ];
+          boot.loader = {
+            grub = {
+              version = 2;
+              device = "/dev/vda";
+            };
+            timeout = 0;
+          };
+        };
+        config = (import (pkgs.path + "/nixos/lib/eval-config.nix") {
+          inherit pkgs; modules = [ qemuConfig ];
+          system = "x86_64-linux";
+        }).config;
+      in
+      import (pkgs.path + "/nixos/lib/make-disk-image.nix") {
+        inherit pkgs lib config;
+        diskSize = 16000;
+        format = "qcow2-compressed";
+        contents = [
+          { source = pkgs.writeText "gitconfig" ''
+              [user]
+                name = builds.sr.ht
+                email = build@sr.ht
+            '';
+            target = "/home/build/.gitconfig";
+            user = "build";
+            group = "users";
+            mode = "644";
+          }
+        ];
+      };
+  };
+
+in
 {
   name = "sourcehut";
 
   meta.maintainers = [ pkgs.lib.maintainers.tomberek ];
 
-  machine = { config, pkgs, ... }: {
-    virtualisation.memorySize = 2048;
-    networking.firewall.allowedTCPPorts = [ 80 ];
+  machine = { config, pkgs, nodes, ... }: {
+    # buildsrht needs space
+    virtualisation.diskSize = 4 * 1024;
+    virtualisation.memorySize = 2 * 1024;
+    networking.domain = domain;
+    networking.extraHosts = ''
+      ${config.networking.primaryIPAddress} meta.${domain}
+      ${config.networking.primaryIPAddress} builds.${domain}
+    '';
 
     services.sourcehut = {
       enable = true;
-      services = [ "meta" ];
-      originBase = "sourcehut";
-      settings."sr.ht".service-key =   "8888888888888888888888888888888888888888888888888888888888888888";
-      settings."sr.ht".network-key = "0000000000000000000000000000000000000000000=";
-      settings.webhooks.private-key = "0000000000000000000000000000000000000000000=";
+      services = [ "meta" "builds" ];
+      nginx.enable = true;
+      nginx.virtualHost = {
+        forceSSL = true;
+        sslCertificate = "${tls-cert}/cert.pem";
+        sslCertificateKey = "${tls-cert}/key.pem";
+      };
+      postgresql.enable = true;
+      redis.enable = true;
+
+      meta.enable = true;
+      builds = {
+        enable = true;
+        # FIXME: see why it does not seem to activate fully.
+        #enableWorker = true;
+        inherit images;
+      };
+      settings."sr.ht" = {
+        global-domain = config.networking.domain;
+        service-key = pkgs.writeText "service-key" "8b327279b77e32a3620e2fc9aabce491cc46e7d821fd6713b2a2e650ce114d01";
+        network-key = pkgs.writeText "network-key" "cEEmc30BRBGkgQZcHFksiG7hjc6_dK1XR2Oo5Jb9_nQ=";
+      };
+      settings."builds.sr.ht" = {
+        oauth-client-secret = pkgs.writeText "buildsrht-oauth-client-secret" "2260e9c4d9b8dcedcef642860e0504bc";
+        oauth-client-id = "299db9f9c2013170";
+      };
+      settings.webhooks.private-key = pkgs.writeText "webhook-key" "Ra3IjxgFiwG9jxgp4WALQIZw/BMYt30xWiOsqD0J7EA=";
+    };
+
+    networking.firewall.allowedTCPPorts = [ 443 ];
+    security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+    services.nginx = {
+      enable = true;
+      recommendedGzipSettings = true;
+      recommendedOptimisation = true;
+      recommendedTlsSettings = true;
+      recommendedProxySettings = true;
+    };
+
+    services.postgresql = {
+      enable = true;
+      enableTCPIP = false;
+      settings.unix_socket_permissions = "0770";
     };
   };
 
   testScript = ''
     start_all()
     machine.wait_for_unit("multi-user.target")
+
+    # Testing metasrht
+    machine.wait_for_unit("metasrht-api.service")
     machine.wait_for_unit("metasrht.service")
     machine.wait_for_open_port(5000)
-    machine.succeed("curl -sL http://localhost:5000 | grep meta.sourcehut")
+    machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
+    machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
+
+    # Testing buildsrht
+    machine.wait_for_unit("buildsrht.service")
+    machine.wait_for_open_port(5002)
+    machine.succeed("curl -sL http://localhost:5002 | grep builds.${domain}")
+    #machine.wait_for_unit("buildsrht-worker.service")
   '';
 })
diff --git a/nixpkgs/nixos/tests/spark/default.nix b/nixpkgs/nixos/tests/spark/default.nix
index 254cdec6e6b0..025c5a5222e7 100644
--- a/nixpkgs/nixos/tests/spark/default.nix
+++ b/nixpkgs/nixos/tests/spark/default.nix
@@ -3,7 +3,6 @@ import ../make-test-python.nix ({...}: {
 
   nodes = {
     worker = { nodes, pkgs, ... }: {
-      virtualisation.memorySize = 1024;
       services.spark.worker = {
         enable = true;
         master = "master:7077";
diff --git a/nixpkgs/nixos/tests/spike.nix b/nixpkgs/nixos/tests/spike.nix
deleted file mode 100644
index 09035a156418..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/sssd-ldap.nix b/nixpkgs/nixos/tests/sssd-ldap.nix
index e3119348eac7..5c58eaef7146 100644
--- a/nixpkgs/nixos/tests/sssd-ldap.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/step-ca.nix b/nixpkgs/nixos/tests/step-ca.nix
new file mode 100644
index 000000000000..b22bcb060f2b
--- /dev/null
+++ b/nixpkgs/nixos/tests/step-ca.nix
@@ -0,0 +1,76 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    test-certificates = pkgs.runCommandLocal "test-certificates" { } ''
+      mkdir -p $out
+      echo insecure-root-password > $out/root-password-file
+      echo insecure-intermediate-password > $out/intermediate-password-file
+      ${pkgs.step-cli}/bin/step certificate create "Example Root CA" $out/root_ca.crt $out/root_ca.key --password-file=$out/root-password-file --profile root-ca
+      ${pkgs.step-cli}/bin/step certificate create "Example Intermediate CA 1" $out/intermediate_ca.crt $out/intermediate_ca.key --password-file=$out/intermediate-password-file --ca-password-file=$out/root-password-file --profile intermediate-ca --ca $out/root_ca.crt --ca-key $out/root_ca.key
+    '';
+  in
+  {
+    nodes =
+      {
+        caserver =
+          { config, pkgs, ... }: {
+            services.step-ca = {
+              enable = true;
+              address = "0.0.0.0";
+              port = 8443;
+              openFirewall = true;
+              intermediatePasswordFile = "${test-certificates}/intermediate-password-file";
+              settings = {
+                dnsNames = [ "caserver" ];
+                root = "${test-certificates}/root_ca.crt";
+                crt = "${test-certificates}/intermediate_ca.crt";
+                key = "${test-certificates}/intermediate_ca.key";
+                db = {
+                  type = "badger";
+                  dataSource = "/var/lib/step-ca/db";
+                };
+                authority = {
+                  provisioners = [
+                    {
+                      type = "ACME";
+                      name = "acme";
+                    }
+                  ];
+                };
+              };
+            };
+          };
+
+        caclient =
+          { config, pkgs, ... }: {
+            security.acme.server = "https://caserver:8443/acme/acme/directory";
+            security.acme.email = "root@example.org";
+            security.acme.acceptTerms = true;
+
+            security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
+
+            networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+            services.nginx = {
+              enable = true;
+              virtualHosts = {
+                "caclient" = {
+                  forceSSL = true;
+                  enableACME = true;
+                };
+              };
+            };
+          };
+
+        catester = { config, pkgs, ... }: {
+          security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
+        };
+      };
+
+    testScript =
+      ''
+        catester.start()
+        caserver.wait_for_unit("step-ca.service")
+        caclient.wait_for_unit("acme-finished-caclient.target")
+        catester.succeed("curl https://caclient/ | grep \"Welcome to nginx!\"")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/sway.nix b/nixpkgs/nixos/tests/sway.nix
index 01240ef572a6..3476ebab3e26 100644
--- a/nixpkgs/nixos/tests/sway.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/switch-test.nix b/nixpkgs/nixos/tests/switch-test.nix
index 4caa7d98f47f..daad9134885f 100644
--- a/nixpkgs/nixos/tests/switch-test.nix
+++ b/nixpkgs/nixos/tests/switch-test.nix
@@ -3,81 +3,16 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "switch-test";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ gleber ];
+    maintainers = [ gleber das_j ];
   };
 
   nodes = {
-    machine = { config, pkgs, lib, ... }: {
-      environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff
+    machine = { pkgs, lib, ... }: {
       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;
+      specialisation = rec {
+        simpleService.configuration = {
+          systemd.services.test = {
             wantedBy = [ "multi-user.target" ];
             serviceConfig = {
               Type = "oneshot";
@@ -85,74 +20,32 @@ import ./make-test-python.nix ({ pkgs, ...} : {
               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";
-            };
-          };
+        simpleServiceModified.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.serviceConfig.X-Test = 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
-            '';
-          };
+        simpleServiceNostop.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.stopIfChanged = false;
         };
 
-        # 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";
-            };
+        simpleServiceReload.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test = {
+            reloadIfChanged = true;
+            serviceConfig.ExecReload = "${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";
+        simpleServiceNorestart.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.restartIfChanged = false;
         };
 
-        # A system with a systemd mount
-        with-mount.configuration = {
+        mount.configuration = {
           systemd.mounts = [
             {
               description = "Testmount";
@@ -165,8 +58,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           ];
         };
 
-        # The same system but with another time
-        with-mount-modified.configuration = {
+        mountModified.configuration = {
           systemd.mounts = [
             {
               description = "Testmount";
@@ -179,8 +71,25 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           ];
         };
 
-        # A system with a path unit
-        with-path.configuration = {
+        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";
+            };
+          };
+        };
+
+        timerModified.configuration = {
+          imports = [ timer.configuration ];
+          systemd.timers.test-timer.timerConfig.OnCalendar = lib.mkForce "Fri 2012-11-23 16:00:00";
+        };
+
+        path.configuration = {
           systemd.paths.test-watch = {
             wantedBy = [ "paths.target" ];
             pathConfig.PathExists = "/testpath";
@@ -193,14 +102,12 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           };
         };
 
-        # The same system but watching another file
-        with-path-modified.configuration = {
-          imports = [ config.specialisation.with-path.configuration ];
+        pathModified.configuration = {
+          imports = [ path.configuration ];
           systemd.paths.test-watch.pathConfig.PathExists = lib.mkForce "/testpath2";
         };
 
-        # A system with a slice
-        with-slice.configuration = {
+        slice.configuration = {
           systemd.slices.testslice.sliceConfig.MemoryMax = "1"; # don't allow memory allocation
           systemd.services.testservice = {
             serviceConfig = {
@@ -212,14 +119,14 @@ import ./make-test-python.nix ({ pkgs, ...} : {
           };
         };
 
-        # The same system but the slice allows to allocate memory
-        with-slice-non-crashing.configuration = {
-          imports = [ config.specialisation.with-slice.configuration ];
+        sliceModified.configuration = {
+          imports = [ slice.configuration ];
           systemd.slices.testslice.sliceConfig.MemoryMax = lib.mkForce null;
         };
       };
     };
-    other = { ... }: {
+
+    other = {
       users.mutableUsers = true;
     };
   };
@@ -227,6 +134,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   testScript = { nodes, ... }: let
     originalSystem = nodes.machine.config.system.build.toplevel;
     otherSystem = nodes.other.config.system.build.toplevel;
+    machine = nodes.machine.config.system.build.toplevel;
 
     # Ensures failures pass through using pipefail, otherwise failing to
     # switch-to-configuration is hidden by the success of `tee`.
@@ -237,8 +145,12 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       exec env -i "$@" | tee /dev/stderr
     '';
   in /* python */ ''
-    def switch_to_specialisation(name, action="test"):
-        out = machine.succeed(f"${originalSystem}/specialisation/{name}/bin/switch-to-configuration {action} 2>&1")
+    def switch_to_specialisation(system, name, action="test"):
+        if name == "":
+            stc = f"{system}/bin/switch-to-configuration"
+        else:
+            stc = f"{system}/specialisation/{name}/bin/switch-to-configuration"
+        out = machine.succeed(f"{stc} {action} 2>&1")
         assert_lacks(out, "switch-to-configuration line")  # Perl warnings
         return out
 
@@ -266,101 +178,98 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test"
     )
 
-    with subtest("systemd sockets"):
-        machine.succeed("${originalSystem}/bin/switch-to-configuration test")
+    with subtest("services"):
+        switch_to_specialisation("${machine}", "")
+        # Nothing happens when nothing is changed
+        out = switch_to_specialisation("${machine}", "")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
 
-        # Simple socket is created
-        out = switch_to_specialisation("simple-socket")
+        # Start a simple service
+        out = switch_to_specialisation("${machine}", "simpleService")
         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, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")  # huh
+        assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
-        assert_contains(out, "the following new units were started: socket-activated.socket\n")
+        assert_contains(out, "the following new units were started: test.service\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")
+        # Not changing anything doesn't do anything
+        out = switch_to_specialisation("${machine}", "simpleService")
         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, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
         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")
+        # Restart the simple service
+        out = switch_to_specialisation("${machine}", "simpleServiceModified")
+        assert_contains(out, "stopping the following units: test.service\n")
+        assert_lacks(out, "NOT restarting the following changed 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, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: test.service\n")
         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")
+        # Restart the service with stopIfChanged=false
+        out = switch_to_specialisation("${machine}", "simpleServiceNostop")
         assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
         assert_lacks(out, "reloading the following units:")
-        assert_lacks(out, "restarting the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
         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:")
+        # Reload the service with reloadIfChanged=true
+        out = switch_to_specialisation("${machine}", "simpleServiceReload")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: test.service\n")
+        assert_lacks(out, "\nrestarting 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")
+        # Nothing happens when restartIfChanged=false
+        out = switch_to_specialisation("${machine}", "simpleServiceNorestart")
+        assert_lacks(out, "stopping the following units:")
+        assert_contains(out, "NOT restarting the following changed units: test.service\n")
         assert_lacks(out, "reloading the following units:")
-        assert_contains(out, "restarting the following units: socket-activated.socket\n")
+        assert_lacks(out, "\nrestarting the following units:")
         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")
+        # Dry mode shows different messages
+        out = switch_to_specialisation("${machine}", "simpleService", action="dry-activate")
         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")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_lacks(out, "as well:")
+        assert_contains(out, "would start the following units: test.service\n")
 
     with subtest("mounts"):
-        switch_to_specialisation("with-mount")
+        switch_to_specialisation("${machine}", "mount")
         out = machine.succeed("mount | grep 'on /testmount'")
         assert_contains(out, "size=1024k")
-
-        out = switch_to_specialisation("with-mount-modified")
+        out = switch_to_specialisation("${machine}", "mountModified")
         assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
         assert_contains(out, "reloading the following units: testmount.mount\n")
-        assert_lacks(out, "restarting the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
         assert_lacks(out, "\nstarting the following units:")
         assert_lacks(out, "the following new units were started:")
         assert_lacks(out, "as well:")
@@ -369,11 +278,10 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "size=10240k")
 
     with subtest("timers"):
-        switch_to_specialisation("with-timer")
+        switch_to_specialisation("${machine}", "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")
+        out = switch_to_specialisation("${machine}", "timerModified")
         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")
@@ -385,16 +293,21 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         assert_contains(out, "OnCalendar=Fri 2012-11-23 16:00:00")
 
     with subtest("paths"):
-        switch_to_specialisation("with-path")
+        out = switch_to_specialisation("${machine}", "path")
+        assert_contains(out, "stopping the following units: test-timer.timer\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test-watch.path")
+        assert_lacks(out, "as well:")
         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 /testpath-modified")
-        switch_to_specialisation("with-path-modified")
-
+        switch_to_specialisation("${machine}", "pathModified")
         machine.succeed("touch /testpath")
         machine.fail("test -f /testpath-modified")
         machine.succeed("touch /testpath2")
@@ -407,11 +320,10 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     # 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")
+        out = switch_to_specialisation("${machine}", "slice")
         machine.fail("systemctl start testservice.service")
-        out = switch_to_specialisation("with-slice-non-crashing")
+        out = switch_to_specialisation("${machine}", "sliceModified")
         machine.succeed("systemctl start testservice.service")
         machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom")  # disallow OOMing
-
   '';
 })
diff --git a/nixpkgs/nixos/tests/sympa.nix b/nixpkgs/nixos/tests/sympa.nix
index eb38df180a78..aad7c95b6c99 100644
--- a/nixpkgs/nixos/tests/sympa.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/systemd-boot.nix b/nixpkgs/nixos/tests/systemd-boot.nix
index 3c93cb82d646..c3899b58d6b3 100644
--- a/nixpkgs/nixos/tests/systemd-boot.nix
+++ b/nixpkgs/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";
@@ -79,12 +102,12 @@ in
       machine.succeed(
           """
         find /boot -iname '*.efi' -print0 | \
-        xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 001 ####/' '{}'
+        xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}'
       """
       )
 
       output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
-      assert "updating systemd-boot from 001 to " in output
+      assert "updating systemd-boot from (000.0-1-notnixos) to " in output
     '';
   };
 }
diff --git a/nixpkgs/nixos/tests/systemd-cryptenroll.nix b/nixpkgs/nixos/tests/systemd-cryptenroll.nix
new file mode 100644
index 000000000000..49634ef65672
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix b/nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
new file mode 100644
index 000000000000..a8254a158016
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
@@ -0,0 +1,81 @@
+# In contrast to systemd-networkd-dhcpserver, this test configures
+# the router with a static DHCP lease for the client's MAC address.
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-networkd-dhcpserver-static-leases";
+  meta = with lib.maintainers; {
+    maintainers = [ veehaitch tomfitzhenry ];
+  };
+  nodes = {
+    router = {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+      systemd.network = {
+        networks = {
+          # systemd-networkd will load the first network unit file
+          # that matches, ordered lexiographically by filename.
+          # /etc/systemd/network/{40-eth1,99-main}.network already
+          # exists. This network unit must be loaded for the test,
+          # however, hence why this network is named such.
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              DHCPServer = true;
+              Address = "10.0.0.1/24";
+            };
+            dhcpServerStaticLeases = [{
+              dhcpServerStaticLeaseConfig = {
+                MACAddress = "02:de:ad:be:ef:01";
+                Address = "10.0.0.10";
+              };
+            }];
+          };
+        };
+      };
+    };
+
+    client = {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1 = {
+          useDHCP = true;
+          macAddress = "02:de:ad:be:ef:01";
+        };
+      };
+
+      # This setting is important to have the router assign the
+      # configured lease based on the client's MAC address. Also see:
+      # https://github.com/systemd/systemd/issues/21368#issuecomment-982193546
+      systemd.network.networks."40-eth1".dhcpV4Config.ClientIdentifier = "mac";
+    };
+  };
+  testScript = ''
+    start_all()
+
+    with subtest("check router network configuration"):
+      router.wait_for_unit("systemd-networkd-wait-online.service")
+      eth1_status = router.succeed("networkctl status eth1")
+      assert "Network File: /etc/systemd/network/01-eth1.network" in eth1_status, \
+        "The router interface eth1 is not using the expected network file"
+      assert "10.0.0.1" in eth1_status, "Did not find expected router IPv4"
+
+    with subtest("check client network configuration"):
+      client.wait_for_unit("systemd-networkd-wait-online.service")
+      eth1_status = client.succeed("networkctl status eth1")
+      assert "Network File: /etc/systemd/network/40-eth1.network" in eth1_status, \
+        "The client interface eth1 is not using the expected network file"
+      assert "10.0.0.10" in eth1_status, "Did not find expected client IPv4"
+
+    with subtest("router and client can reach each other"):
+      client.wait_until_succeeds("ping -c 5 10.0.0.1")
+      router.wait_until_succeeds("ping -c 5 10.0.0.10")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd.nix b/nixpkgs/nixos/tests/systemd.nix
index e0685f53a945..f86daa5eea97 100644
--- a/nixpkgs/nixos/tests/systemd.nix
+++ b/nixpkgs/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 ];
 
@@ -32,6 +31,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       umount /tmp/shared
     '';
 
+    systemd.services.oncalendar-test = {
+      description = "calendar test";
+      # Japan does not have DST which makes the test a little bit simpler
+      startAt = "Wed 10:00 Asia/Tokyo";
+      script = "true";
+    };
+
     systemd.services.testservice1 = {
       description = "Test Service 1";
       wantedBy = [ "multi-user.target" ];
@@ -70,6 +76,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     # wait for user services
     machine.wait_for_unit("default.target", "alice")
 
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/105049
+    with subtest("systemd reads timezone database in /etc/zoneinfo"):
+        timer = machine.succeed("TZ=UTC systemctl show --property=TimersCalendar oncalendar-test.timer")
+        assert re.search("next_elapse=Wed ....-..-.. 01:00:00 UTC", timer), f"got {timer.strip()}"
+
     # Regression test for https://github.com/NixOS/nixpkgs/issues/35415
     with subtest("configuration files are recognized by systemd"):
         machine.succeed("test -e /system_conf_read")
diff --git a/nixpkgs/nixos/tests/tigervnc.nix b/nixpkgs/nixos/tests/tigervnc.nix
index 092eaf238d80..ed575682d933 100644
--- a/nixpkgs/nixos/tests/tigervnc.nix
+++ b/nixpkgs/nixos/tests/tigervnc.nix
@@ -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/nixpkgs/nixos/tests/tinydns.nix b/nixpkgs/nixos/tests/tinydns.nix
index b80e3451700a..124508bc004b 100644
--- a/nixpkgs/nixos/tests/tinydns.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/trac.nix b/nixpkgs/nixos/tests/trac.nix
deleted file mode 100644
index d6914c100817..000000000000
--- a/nixpkgs/nixos/tests/trac.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "trac";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ mmahut ];
-  };
-
-  nodes = {
-    machine = { ... }: {
-      services.trac.enable = true;
-    };
-  };
-
-  testScript = ''
-    start_all()
-    machine.wait_for_unit("trac.service")
-    machine.wait_for_open_port(8000)
-    machine.wait_until_succeeds("curl -fL http://localhost:8000/ | grep 'Trac Powered'")
-  '';
-})
diff --git a/nixpkgs/nixos/tests/turbovnc-headless-server.nix b/nixpkgs/nixos/tests/turbovnc-headless-server.nix
index dfa17d65f85e..7d705c56ecf3 100644
--- a/nixpkgs/nixos/tests/turbovnc-headless-server.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/tuxguitar.nix b/nixpkgs/nixos/tests/tuxguitar.nix
index 6586132d3cd4..63a7b6c7dec9 100644
--- a/nixpkgs/nixos/tests/tuxguitar.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/txredisapi.nix b/nixpkgs/nixos/tests/txredisapi.nix
index bc3814a71375..7c6b36a5c47d 100644
--- a/nixpkgs/nixos/tests/txredisapi.nix
+++ b/nixpkgs/nixos/tests/txredisapi.nix
@@ -10,17 +10,19 @@ import ./make-test-python.nix ({ pkgs, ... }:
       { pkgs, ... }:
 
       {
-        services.redis.enable = true;
-        services.redis.unixSocket = "/run/redis/redis.sock";
+        services.redis.servers."".enable = true;
 
         environment.systemPackages = with pkgs; [ (python38.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))];
       };
   };
 
-  testScript = ''
+  testScript = { nodes, ... }: let
+    inherit (nodes.machine.config.services) redis;
+    in ''
     start_all()
     machine.wait_for_unit("redis")
-    machine.wait_for_open_port("6379")
+    machine.wait_for_file("${redis.servers."".unixSocket}")
+    machine.succeed("ln -s ${redis.servers."".unixSocket} /tmp/redis.sock")
 
     tests = machine.succeed("PYTHONPATH=\"${pkgs.python3Packages.txredisapi.src}\" python -m twisted.trial ${pkgs.python3Packages.txredisapi.src}/tests")
   '';
diff --git a/nixpkgs/nixos/tests/ucg.nix b/nixpkgs/nixos/tests/ucg.nix
deleted file mode 100644
index 7769fd01fce4..000000000000
--- a/nixpkgs/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/nixpkgs/nixos/tests/unifi.nix b/nixpkgs/nixos/tests/unifi.nix
new file mode 100644
index 000000000000..34284811abfb
--- /dev/null
+++ b/nixpkgs/nixos/tests/unifi.nix
@@ -0,0 +1,35 @@
+# Test UniFi controller
+
+{ system ? builtins.currentSystem
+, config ? { allowUnfree = true; }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  makeAppTest = unifi: makeTest {
+    name = "unifi-controller-${unifi.version}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ zhaofengli ];
+    };
+
+    nodes.server = {
+      services.unifi = {
+        enable = true;
+        unifiPackage = unifi;
+        openFirewall = false;
+      };
+    };
+
+    testScript = ''
+      server.wait_for_unit("unifi.service")
+      server.wait_until_succeeds("curl -Lk https://localhost:8443 >&2", timeout=300)
+    '';
+  };
+in with pkgs; {
+  unifiLTS = makeAppTest unifiLTS;
+  unifi5 = makeAppTest unifi5;
+  unifi6 = makeAppTest unifi6;
+}
diff --git a/nixpkgs/nixos/tests/vault-postgresql.nix b/nixpkgs/nixos/tests/vault-postgresql.nix
index a563aead22a3..2847af13cbf0 100644
--- a/nixpkgs/nixos/tests/vault-postgresql.nix
+++ b/nixpkgs/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;
@@ -65,6 +64,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
       machine.wait_for_unit("vault.service")
       machine.wait_for_open_port(8200)
       machine.succeed("vault operator init")
-      machine.succeed("vault status | grep Sealed | grep true")
+      machine.succeed("vault status || test $? -eq 2")
     '';
 })
diff --git a/nixpkgs/nixos/tests/vault.nix b/nixpkgs/nixos/tests/vault.nix
index c3b28b62695a..e86acd5b593f 100644
--- a/nixpkgs/nixos/tests/vault.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/vaultwarden.nix b/nixpkgs/nixos/tests/vaultwarden.nix
index b5343f5cad2d..56f1d245d505 100644
--- a/nixpkgs/nixos/tests/vaultwarden.nix
+++ b/nixpkgs/nixos/tests/vaultwarden.nix
@@ -140,7 +140,6 @@ let
               in
               [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
 
-            virtualisation.memorySize = 768;
           }
         ];
 
diff --git a/nixpkgs/nixos/tests/vengi-tools.nix b/nixpkgs/nixos/tests/vengi-tools.nix
new file mode 100644
index 000000000000..6b90542887d5
--- /dev/null
+++ b/nixpkgs/nixos/tests/vengi-tools.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vengi-tools";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.vengi-tools ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("vengi-voxedit >&2 &")
+      machine.wait_for_window("voxedit")
+      # OCR on voxedit's window is very expensive, so we avoid wasting a try
+      # by letting the window load fully first
+      machine.sleep(15)
+      machine.wait_for_text("Palette")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/virtualbox.nix b/nixpkgs/nixos/tests/virtualbox.nix
index 09314d93b7d0..f15412d365fa 100644
--- a/nixpkgs/nixos/tests/virtualbox.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/vscodium.nix b/nixpkgs/nixos/tests/vscodium.nix
index ca75da35b1e1..43a0d61c856f 100644
--- a/nixpkgs/nixos/tests/vscodium.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/web-apps/peertube.nix b/nixpkgs/nixos/tests/web-apps/peertube.nix
new file mode 100644
index 000000000000..38b31f6c3325
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/wine.nix b/nixpkgs/nixos/tests/wine.nix
new file mode 100644
index 000000000000..c46c7d338b2e
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/xfce.nix b/nixpkgs/nixos/tests/xfce.nix
index 99e30342e593..9051deebae76 100644
--- a/nixpkgs/nixos/tests/xfce.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/xrdp.nix b/nixpkgs/nixos/tests/xrdp.nix
index 92eb7d4772ef..0e1d521c5ace 100644
--- a/nixpkgs/nixos/tests/xrdp.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/xterm.nix b/nixpkgs/nixos/tests/xterm.nix
index 078d1dca9642..4ee31139ab52 100644
--- a/nixpkgs/nixos/tests/xterm.nix
+++ b/nixpkgs/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/nixpkgs/nixos/tests/yq.nix b/nixpkgs/nixos/tests/yq.nix
deleted file mode 100644
index cdcb3d6e2462..000000000000
--- a/nixpkgs/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 .'
-    )
-  '';
-})